Message ID | 1357299303-30138-1-git-send-email-colin.king@canonical.com |
---|---|
State | Rejected |
Headers | show |
On Fri, Jan 4, 2013 at 7:35 PM, Colin King <colin.king@canonical.com> wrote: > From: Colin Ian King <colin.king@canonical.com> > > We're occasionally seeing some firmware with invalid or buggy > _CRS buffers being passed to the kernel and ACPI 5.0 has added > some new _CRS features so it seems prudent to sanity check the > buffers a little more. > > This patch adds some _CRS sanity checking for small and large > resource types as defined by the ACPI 5.0 specification. I've run > this against my database of ACPI tables and it does seem to catch > a few minor and obscure _CRS issues. > > Signed-off-by: Colin Ian King <colin.king@canonical.com> > --- > src/acpi/method/method.c | 664 ++++++++++++++++++++++++++++++++++++++++++++++- > 1 file changed, 662 insertions(+), 2 deletions(-) > > diff --git a/src/acpi/method/method.c b/src/acpi/method/method.c > index 0b8dafd..76b9f9e 100644 > --- a/src/acpi/method/method.c > +++ b/src/acpi/method/method.c > @@ -945,14 +945,674 @@ static int method_test_UID(fwts_framework *fw) > "_UID", NULL, 0, method_test_UID_return, NULL); > } > > - > /* > * Section 6.2 Device Configurations Objects > */ > +static void method_test_CRS_size( > + fwts_framework *fw, > + const char *name, /* full _CRS path name */ > + const char *tag, /* error log tag */ > + const size_t crs_length, /* size of _CRS buffer */ > + const size_t hdr_length, /* size of _CRS header */ > + const size_t data_length, /* length of _CRS data w/o header */ > + const size_t min, /* minimum allowed _CRS data size */ > + const size_t max, /* maximum allowed _CRS data size */ > + bool *passed) /* pass/fail flag */ > +{ > + if (crs_length < data_length + hdr_length) { > + fwts_failed(fw, LOG_LEVEL_HIGH, tag, > + "%s Resource size is %zd bytes long but " > + "the size stated in the _CRS buffer header " > + "is %zd and hence is longer. The resource " > + "buffer is too short.", > + name, crs_length, data_length); > + *passed = false; > + return; > + } > + > + if ((data_length < min) || (data_length > max)) { > + if (min != max) { > + fwts_failed(fw, LOG_LEVEL_MEDIUM, tag, > + "%s Resource data size was %zd bytes long, " > + "expected it to be between %zd and %zd bytes", > + name, data_length, min, max); > + *passed = false; > + } else { > + fwts_failed(fw, LOG_LEVEL_MEDIUM, tag, > + "%s Resource data size was %zd bytes long, " > + "expected it to be %zd bytes", > + name, data_length, min); > + *passed = false; > + } > + } > +} > + > +static void method_test_CRS_small_size( > + fwts_framework *fw, > + const char *name, > + const uint8_t *data, > + const size_t crs_length, > + const size_t min, > + const size_t max, > + bool *passed) > +{ > + size_t data_length = data[0] & 7; > + > + method_test_CRS_size(fw, name, "Method_CRSSmallResourceSize", > + crs_length, 1, data_length, min, max, passed); > +} > + > + > +/* > + * CRS small resource checks, simple checking > + */ > +static void method_test_CRS_small_resource_items( > + fwts_framework *fw, > + const char *name, > + const uint8_t *data, > + const size_t length, > + bool *passed, > + const char **tag) > +{ > + uint8_t tag_item = (data[0] >> 3) & 0xf; > + > + static const char *types[] = { > + "Reserved", > + "Reserved", > + "Reserved", > + "Reserved", > + "IRQ Descriptor", > + "DMA Descriptor", > + "Start Dependent Functions Descriptor", > + "End Dependent Functions Descriptor", > + "I/O Port Descriptor", > + "Fixed Location I/O Port Descriptor", > + "Fixed DMA Descriptor", > + "Reserved", > + "Reserved", > + "Reserved", > + "Vendor Defined Descriptor", > + "End Tag Descriptor" > + }; > + > + switch (tag_item) { > + case 0x4: /* 6.4.2.1 IRQ Descriptor */ > + method_test_CRS_small_size(fw, name, data, length, 2, 3, passed); > + break; > + case 0x5: /* 6.4.2.2 DMA Descriptor */ > + method_test_CRS_small_size(fw, name, data, length, 2, 2, passed); > + if (!*passed) /* Too short, abort */ > + break; > + if ((data[2] & 3) == 3) { > + fwts_failed(fw, LOG_LEVEL_HIGH, > + "Method_CRSDmaDescriptor", > + "%s DMA transfer type preference is 0x%" PRIx8 > + " which is reserved and invalid. See " > + "Section 6.4.2.2 of the ACPI specification.", > + name, data[2] & 3); > + *passed = false; > + } > + break; > + case 0x6: /* 6.4.2.3 Start Dependent Functions Descriptor */ > + method_test_CRS_small_size(fw, name, data, length, 0, 1, passed); > + break; > + case 0x7: /* 6.4.2.4 End Dependent Functions Descriptor */ > + method_test_CRS_small_size(fw, name, data, length, 0, 0, passed); > + break; > + case 0x8: /* 6.4.2.5 I/O Port Descriptor */ > + method_test_CRS_small_size(fw, name, data, length, 7, 7, passed); > + if (!*passed) /* Too short, abort */ > + break; > + if (data[1] & 0xfe) { > + fwts_failed(fw, LOG_LEVEL_LOW, > + "Method_CRSIoPortInfoReservedNonZero", > + "%s I/O Port Descriptor Information field " > + "has reserved bits that are non-zero, got " > + "0x%" PRIx8 " and expected 0 or 1 for this " > + "field. ", name, data[1]); > + *passed = false; > + } > + if (((data[1] & 1) == 0) && (data[3] > 3)) { > + fwts_failed(fw, LOG_LEVEL_LOW, > + "Method_CRSIoPortInfoMinBase10BitAddr", > + "%s I/O Port Descriptor range minimum " > + "base address is more than 10 bits however " > + "the Information field indicates that only " > + "a 10 bit address is being used.", name); > + *passed = false; > + } > + if (((data[1] & 1) == 0) && (data[5] > 3)) { > + fwts_failed(fw, LOG_LEVEL_LOW, > + "Method_CRSIoPortInfoMinBase10BitAddr", > + "%s I/O Port Descriptor range maximum " > + "base address is more than 10 bits however " > + "the Information field indicates that only " > + "a 10 bit address is being used.", name); > + *passed = false; > + } > + break; > + case 0x9: /* 6.4.2.6 Fixed Location I/O Port Descriptor */ > + method_test_CRS_small_size(fw, name, data, length, 3, 3, passed); > + break; > + case 0xa: /* 6.4.2.7 Fixed DMA Descriptor */ > + method_test_CRS_small_size(fw, name, data, length, 5, 5, passed); > + if (!*passed) /* Too short, abort */ > + break; > + if (data[5] > 5) { > + fwts_failed(fw, LOG_LEVEL_HIGH, > + "Method_CRSFixedDmaTransferWidth", > + "%s DMA transfer width is 0x%" PRIx8 > + " which is reserved and invalid. See " > + "Section 6.4.2.7 of the ACPI specification.", > + name, data[5]); > + *passed = false; > + } > + break; > + case 0xe: /* 6.4.2.8 Vendor-Defined Descriptor */ > + method_test_CRS_small_size(fw, name, data, length, 1, 7, passed); > + break; > + case 0xf: /* 6.4.2.9 End Tag */ > + method_test_CRS_small_size(fw, name, data, length, 1, 1, passed); > + break; > + default: > + fwts_failed(fw, LOG_LEVEL_LOW, > + "Method_CRSUnkownSmallResourceItem", > + "%s tag bits 6:3 is an undefined " > + "small tag item name, value 0x%" PRIx8 ".", > + name, tag_item); > + fwts_advice(fw, > + "A small resource data type tag (byte 0, " > + "bits 6:3 of the _CRS buffer) contains " > + "an undefined small tag item 'name'. " > + "The _CRS buffer is therefore undefined " > + "and can't be used. See section " > + "'6.4.2 Small Resource Data Type' of the ACPI " > + "specification, and also table 6-161."); > + *passed = false; > + break; > + } > + > + *tag = types[tag_item]; > +} > + > +static void method_test_CRS_large_size( > + fwts_framework *fw, > + const char *name, > + const uint8_t *data, > + const size_t crs_length, > + const size_t min, > + const size_t max, > + bool *passed) > +{ > + size_t data_length; > + > + /* Small _CRS resources have a 3 byte header */ > + if (crs_length < 3) { > + fwts_failed(fw, LOG_LEVEL_MEDIUM, "Method_CRSBufferTooSmall", > + "%s should return a buffer of at least three bytes in length.", name); > + *passed = false; > + return; > + } > + > + data_length = (size_t)data[1] + ((size_t)data[2] << 8); > + > + method_test_CRS_size(fw, name, "Method_CRSLargeResourceSize", > + crs_length, 3, data_length, min, max, passed); > +} > + > +/* > + * Some CRS value fetching helper functions. We handle all the > + * addresses and lengths in 64 bits to make life easier > + */ > +static uint64_t method_CRS_val64(const uint8_t *data) > +{ > + uint64_t val = > + ((uint64_t)data[7] << 56) | ((uint64_t)data[6] << 48) | > + ((uint64_t)data[5] << 40) | ((uint64_t)data[0] << 32) | > + ((uint64_t)data[3] << 24) | ((uint64_t)data[2] << 16) | > + ((uint64_t)data[1] << 8) | (uint64_t)data[0]; > + > + return val; > +} > + > +static uint64_t method_CRS_val32(const uint8_t *data) > +{ > + uint64_t val = > + ((uint64_t)data[3] << 24) | ((uint64_t)data[2] << 16) | > + ((uint64_t)data[1] << 8) | (uint64_t)data[0]; > + > + return val; > +} > + > +static uint64_t method_CRS_val24(const uint8_t *data) > +{ > + /* 24 bit values assume lower 8 bits are zero */ > + uint64_t val = > + ((uint64_t)data[1] << 16) | ((uint64_t)data[0] << 8); > + > + return val; > +} > + > +static uint64_t method_CRS_val16(const uint8_t *data) > +{ > + uint64_t val = > + ((uint64_t)data[1] << 8) | (uint64_t)data[0]; > + > + return val; > +} > + > +/* > + * Sanity check addresses according to table 6-179 of ACPI spec > + */ > +static void method_test_CRS_mif_maf( > + fwts_framework *fw, > + const char *name, /* Full _CRS path name */ > + const uint8_t flag, /* _MIF _MAF flag field */ > + const uint64_t min, /* Min address */ > + const uint64_t max, /* Max address */ > + const uint64_t len, /* Range length */ > + const uint64_t granularity, /* Address granularity */ > + const char *tag, /* failed error tag */ > + const char *type, /* Resource type */ > + bool *passed) > +{ > + char tmp[128]; > + uint8_t mif = (flag >> 2) & 1; > + uint8_t maf = (flag >> 3) & 1; > + > + static char *mif_maf_advice = > + "See section '6.4.3.5 Address Space Resource Descriptors' " > + "table 6-179 of the ACPI specification for more details " > + "about how the _MIF, _MAF and memory range and granularity " > + "rules apply. Typically the kernel does not care about these " > + "being correct, so this is a minor issue."; > + > + /* Table 6-179 Valid combination of Address Space Descriptors fields */ > + if (len == 0) { > + if ((mif == 1) && (maf == 1)) { > + snprintf(tmp, sizeof(tmp), "Method_CRS%sMifMafBothOne", tag); > + fwts_failed(fw, LOG_LEVEL_MEDIUM, > + tmp, > + "%s %s _MIF and _MAF flags are both " > + "set to one which is invalid when " > + "the length field is 0.", > + name, type); > + fwts_advice(fw, "%s", mif_maf_advice); > + *passed = false; > + } > + if ((mif == 1) && (min % (granularity + 1) != 0)) { > + snprintf(tmp, sizeof(tmp), "Method_CRS%sMinNotMultipleOfGran", tag); > + fwts_failed(fw, LOG_LEVEL_MEDIUM, > + tmp, > + "%s %s _MIN address is not a multiple " > + "of the granularity when _MIF is 1.", > + name, type); > + fwts_advice(fw, "%s", mif_maf_advice); > + *passed = false; > + } > + if ((maf == 1) && (max % (granularity - 1) != 0)) { > + snprintf(tmp, sizeof(tmp), "Method_CRS%sMaxNotMultipleOfGran", tag); > + fwts_failed(fw, LOG_LEVEL_MEDIUM, > + tmp, > + "%s %s _MAX address is not a multiple " > + "of the granularity when _MAF is 1.", > + name, type); > + fwts_advice(fw, "%s", mif_maf_advice); > + *passed = false; > + } > + } else { > + if ((mif == 0) && (maf == 0) && > + (len % (granularity + 1) != 0)) { > + snprintf(tmp, sizeof(tmp), "Method_CRS%sLenNotMultipleOfGran", tag); > + fwts_failed(fw, LOG_LEVEL_MEDIUM, > + tmp, > + "%s %s length is not a multiple " > + "of the granularity when _MIF " > + "and _MIF are 0.", > + name, type); > + fwts_advice(fw, "%s", mif_maf_advice); > + *passed = false; > + } > + if (((mif == 0) && (maf == 1)) || > + ((mif == 1) && (maf == 0))) { > + snprintf(tmp, sizeof(tmp), "Method_CRS%sMifMafInvalid", tag); > + fwts_failed(fw, LOG_LEVEL_MEDIUM, > + tmp, > + "%s %s _MIF and _MAF flags are either " > + "0 and 1 or 1 and 0 which is invalid when " > + "the length field is non-zero.", > + name, type); > + fwts_advice(fw, "%s", mif_maf_advice); > + *passed = false; > + } > + if ((mif == 1) && (maf == 1)) { > + if (granularity != 0) { > + snprintf(tmp, sizeof(tmp), "Method_CRS%sGranularityNotZero", tag); > + fwts_failed(fw, LOG_LEVEL_MEDIUM, > + tmp, > + "%s %s granularity 0x%" PRIx64 > + " is not zero as expected when " > + "_MIF and _MAF are both 1.", > + name, type, granularity); > + fwts_advice(fw, "%s", mif_maf_advice); > + *passed = false; > + } > + if (min > max) { > + snprintf(tmp, sizeof(tmp), "Method_CRS%sMaxLessThanMin", tag); > + fwts_failed(fw, LOG_LEVEL_MEDIUM, > + tmp, > + "%s %s minimum address range 0x%" PRIx64 > + " is greater than the maximum address " > + "range 0x%" PRIx64 ".", > + name, type, min, max); > + fwts_advice(fw, "%s", mif_maf_advice); > + *passed = false; > + } > + if (max - min + 1 != len) { > + snprintf(tmp, sizeof(tmp), "Method_CRS%sLengthInvalid", tag); > + fwts_failed(fw, LOG_LEVEL_MEDIUM, > + tmp, > + "%s %s length 0x%" PRIx64 > + " does not match the difference between " > + "the minimum and maximum address ranges " > + "0x%" PRIx64 "-0x%" PRIx64 ".", > + name, type, len, min, max); > + fwts_advice(fw, "%s", mif_maf_advice); > + *passed = false; > + } > + } > + } > +} > + > +/* > + * CRS large resource checks, simple checking > + */ > +static void method_test_CRS_large_resource_items( > + fwts_framework *fw, > + const char *name, > + const uint8_t *data, > + const uint64_t length, > + bool *passed, > + const char **tag) > +{ > + uint64_t min, max, len, gra; > + uint8_t tag_item = data[0] & 0x7f; > + > + static const char *types[] = { > + "Reserved", > + "24-bit Memory Range Descriptor", > + "Generic Register Descriptor", > + "Reserved", > + "Vendor Defined Descriptor", > + "32-bit Memory Range Descriptor", > + "32-bit Fixed Location Memory Range Descriptor", > + "DWORD Address Space Descriptor", > + "WORD Address Space Descriptor", > + "Extended IRQ Descriptor", > + "QWORD Address Space Descriptor", > + "Extended Addresss Space Descriptor", > + "GPIO Connection Descriptor", > + "Reserved", > + "Generic Serial Bus Connection Descriptor", > + "Reserved", > + }; > + > + switch (tag_item) { > + case 0x1: /* 6.4.3.1 24-Bit Memory Range Descriptor */ > + method_test_CRS_large_size(fw, name, data, length, 9, 9, passed); > + if (!*passed) /* Too short, abort */ > + break; > + min = method_CRS_val24(&data[4]); > + max = method_CRS_val24(&data[6]); > + len = method_CRS_val16(&data[10]); > + if (max < min) { > + fwts_failed(fw, LOG_LEVEL_MEDIUM, > + "Method_CRS24BitMemRangeMaxLessThanMin", > + "%s 24-Bit Memory Range Descriptor minimum " > + "address range 0x%" PRIx64 " is greater than " > + "the maximum address range 0x%" PRIx64 ".", > + name, min, max); > + *passed = false; > + } > + if (len > max + 1 - min) { > + fwts_failed(fw, LOG_LEVEL_MEDIUM, > + "Method_CRS24BitMemRangeLengthTooLarge", > + "%s 24-Bit Memory Range Descriptor length " > + "0x%" PRIx64 " is greater than size between the " > + "the minimum and maximum address ranges " > + "0x%" PRIx64 "-0x%" PRIx64 ".", > + name, len, min, max); > + *passed = false; > + } > + break; > + case 0x2: /* 6.4.3.7 Generic Register Descriptor */ > + method_test_CRS_large_size(fw, name, data, length, 12, 12, passed); > + if (!*passed) > + break; > + switch (data[3]) { > + case 0x00 ... 0x04: > + case 0x0a: > + case 0x7f: > + /* Valid values */ > + break; > + default: > + fwts_failed(fw, LOG_LEVEL_HIGH, > + "Method_CRSGenericRegAddrSpaceIdInvalid", > + "%s Generic Register Descriptor has an invalid " > + "Address Space ID 0x%" PRIx8 ".", > + name, data[3]); > + *passed = false; > + } > + if (data[6] > 4) { > + fwts_failed(fw, LOG_LEVEL_HIGH, > + "Method_CRSGenericRegAddrSizeInvalid", > + "%s Generic Register Descriptor has an invalid " > + "Address Access Size 0x%" PRIx8 ".", > + name, data[6]); > + *passed = false; > + } > + break; > + case 0x4: /* 6.4.3.2 Vendor-Defined Descriptor */ > + method_test_CRS_large_size(fw, name, data, length, 0, 65535, passed); > + break; > + case 0x5: /* 6.4.3.3 32-Bit Memory Range Descriptor */ > + method_test_CRS_large_size(fw, name, data, length, 17, 17, passed); > + if (!*passed) > + break; > + min = method_CRS_val32(&data[4]); > + max = method_CRS_val32(&data[8]); > + len = method_CRS_val32(&data[16]); > + if (max < min) { > + fwts_failed(fw, LOG_LEVEL_MEDIUM, > + "Method_CRS32BitMemRangeMaxLessThanMin", > + "%s 32-Bit Memory Range Descriptor minimum " > + "address range 0x%" PRIx64 " is greater than " > + "the maximum address range 0x%" PRIx64 ".", > + name, min, max); > + *passed = false; > + } > + if (len > max + 1 - min) { > + fwts_failed(fw, LOG_LEVEL_MEDIUM, > + "Method_CRS32BitMemRangeLengthTooLarge", > + "%s 32-Bit Memory Range Descriptor length " > + "0x%" PRIx64 " is greater than size between the " > + "the minimum and maximum address ranges " > + "0x%" PRIx64 "-0x%" PRIx64 ".", > + name, len, min, max); > + *passed = false; > + } > + break; > + case 0x6: /* 6.4.3.4 32-Bit Fixed Memory Range Descriptor */ > + method_test_CRS_large_size(fw, name, data, length, 9, 9, passed); > + /* Not much can be checked for this descriptor */ > + break; > + case 0x7: /* 6.4.3.5.2 DWord Address Space Descriptor */ > + method_test_CRS_large_size(fw, name, data, length, 23, 65535, passed); > + if (!*passed) /* Too short, abort */ > + break; > + gra = method_CRS_val32(&data[6]); > + min = method_CRS_val32(&data[10]); > + max = method_CRS_val32(&data[14]); > + len = method_CRS_val32(&data[22]); > + > + method_test_CRS_mif_maf(fw, name, data[4], > + min, max, len, gra, > + "64BitDWordAddrSpace", > + types[0x7], passed); > + break; > + case 0x8: /* 6.4.3.5.3 Word Address Space Descriptor */ > + method_test_CRS_large_size(fw, name, data, length, 13, 65535, passed); > + if (!*passed) /* Too short, abort */ > + break; > + gra = method_CRS_val16(&data[6]); > + min = method_CRS_val16(&data[8]); > + max = method_CRS_val16(&data[10]); > + len = method_CRS_val16(&data[14]); > + > + method_test_CRS_mif_maf(fw, name, data[4], > + min, max, len, gra, > + "64BitWordAddrSpace", > + types[0x8], passed); > + break; > + case 0x9: /* 6.4.3.6 Extended Interrupt Descriptor */ > + method_test_CRS_large_size(fw, name, data, length, 6, 65535, passed); > + /* Not much can be checked for this descriptor */ > + break; > + case 0xa: /* 6.4.3.5.1 QWord Address Space Descriptor */ > + method_test_CRS_large_size(fw, name, data, length, 43, 65535, passed); > + if (!*passed) /* Too short, abort */ > + break; > + gra = method_CRS_val64(&data[6]); > + min = method_CRS_val64(&data[14]); > + max = method_CRS_val64(&data[22]); > + len = method_CRS_val64(&data[38]); > + > + method_test_CRS_mif_maf(fw, name, data[4], > + min, max, len, gra, > + "64BitQWordAddrSpace", > + types[0xa], passed); > + break; > + case 0xb: /* 6.4.3.5.4 Extended Address Space Descriptor */ > + method_test_CRS_large_size(fw, name, data, length, 53, 53, passed); > + if (!*passed) /* Too short, abort */ > + break; > + gra = method_CRS_val64(&data[8]); > + min = method_CRS_val64(&data[16]); > + max = method_CRS_val64(&data[24]); > + len = method_CRS_val64(&data[40]); > + > + method_test_CRS_mif_maf(fw, name, data[4], > + min, max, len, gra, > + "64BitExtAddrSpace", > + types[0xb], passed); > + break; > + case 0xc: /* 6.4.3.8.1 GPIO Connection Descriptor */ > + method_test_CRS_large_size(fw, name, data, length, 22, 65535, passed); > + if (!*passed) /* Too short, abort */ > + break; > + if (data[2] > 2) { > + fwts_failed(fw, LOG_LEVEL_MEDIUM, > + "Method_CRSGpioConnTypeInvalid", > + "%s GPIO Connection Descriptor has an invalid " > + "Connection Type 0x%" PRIx8 ".", > + name, data[2]); > + *passed = false; > + fwts_advice(fw, > + "The GPIO pin connection type is " > + "not recognised. It should be either " > + "0x00 (interrupt connection) or " > + "0x01 (I/O connection). See table " > + "6-189 in section 6.4.3.8.1 of the ACPI " > + "specification."); > + } > + if ((data[9] > 0x03) && (data[9] < 0x80)) { > + fwts_failed(fw, LOG_LEVEL_LOW, > + "Method_CRSGpioConnTypeInvalid", > + "%s GPIO Connection Descriptor has an invalid " > + "Pin Configuration Type 0x%" PRIx8 ".", > + name, data[9]); > + *passed = false; > + fwts_advice(fw, > + "The GPIO pin configuration type " > + "is not recognised. It should be one of:" > + "0x00 (default), 0x01 (pull-up), " > + "0x02 (pull-down), 0x03 (no-pull), " > + "0x80-0xff (vendor defined). See table " > + "6-189 in section 6.4.3.8.1 of the ACPI " > + "specification."); > + } > + break; > + case 0xe: /* 6.4.3.8.2 Serial Bus Connection Descriptors */ > + method_test_CRS_large_size(fw, name, data, length, 11, 65535, passed); > + /* Don't care */ > + break; > + default: > + fwts_failed(fw, LOG_LEVEL_LOW, > + "Method_CRSUnkownLargeResourceItem", > + "%s tag bits 6:0 is an undefined " > + "large tag item name, value 0x%" PRIx8 ".", > + name, tag_item); > + fwts_advice(fw, > + "A large resource data type tag (byte 0 of the " > + "_CRS buffer) contains an undefined large tag " > + "item 'name'. The _CRS buffer is therefore " > + "undefined and can't be used. See section " > + "'6.4.3 Large Resource Data Type' of the ACPI " > + "specification, and also table 6-173."); > + *passed = false; > + break; > + } > + > + *tag = types[tag_item < 16 ? tag_item : 0]; > +} > + > +static void method_test_CRS_return( > + fwts_framework *fw, > + char *name, > + ACPI_BUFFER *buf, > + ACPI_OBJECT *obj, > + void *private) > +{ > + uint8_t *data; > + bool passed = true; > + const char *tag = "Unknown"; > + > + FWTS_UNUSED(private); > + > + if (method_check_type(fw, name, buf, ACPI_TYPE_BUFFER) != FWTS_OK) > + return; > + if (obj->Buffer.Pointer == NULL) { > + fwts_failed(fw, LOG_LEVEL_MEDIUM, "Method_CRSNullBuffer", > + "%s returned a NULL buffer pointer.", name); > + return; > + } > + if (obj->Buffer.Length < 1) { > + fwts_failed(fw, LOG_LEVEL_MEDIUM, "Method_CRSBufferTooSmall", > + "%s should return a buffer of at least one byte in length.", name); > + return; > + } > + > + data = (uint8_t*)obj->Buffer.Pointer; > + > + if (data[0] & 128) > + method_test_CRS_large_resource_items(fw, name, data, obj->Buffer.Length, &passed, &tag); > + else > + method_test_CRS_small_resource_items(fw, name, data, obj->Buffer.Length, &passed, &tag); > + > + if (passed) > + fwts_passed(fw, "%s (%s) looks sane.", name, tag); > + else > + fwts_tag_failed(fw, FWTS_TAG_ACPI_METHOD_RETURN); > +} > + > +/* > + * method_test_integer_return > + * check if an integer object was returned > + */ > static int method_test_CRS(fwts_framework *fw) > { > return method_evaluate_method(fw, METHOD_MANDITORY, > - "_CRS", NULL, 0, method_test_buffer_return, NULL); > + "_CRS", NULL, 0, method_test_CRS_return, NULL); > } > > static int method_test_DMA(fwts_framework *fw) > -- > 1.8.0 > Acked-by: Keng-Yu Lin <kengyu@canonical.com>
> + * CRS large resource checks, simple checking > + */ > +static void method_test_CRS_large_resource_items( > + fwts_framework *fw, > + const char *name, > + const uint8_t *data, > + const uint64_t length, > + bool *passed, > + const char **tag) > +{ > + uint64_t min, max, len, gra; > + uint8_t tag_item = data[0] & 0x7f; > + > + static const char *types[] = { > + "Reserved", > + "24-bit Memory Range Descriptor", > + "Generic Register Descriptor", > + "Reserved", > + "Vendor Defined Descriptor", > + "32-bit Memory Range Descriptor", > + "32-bit Fixed Location Memory Range Descriptor", > + "DWORD Address Space Descriptor", > + "WORD Address Space Descriptor", > + "Extended IRQ Descriptor", > + "QWORD Address Space Descriptor", > + "Extended Addresss Space Descriptor", > + "GPIO Connection Descriptor", > + "Reserved", > + "Generic Serial Bus Connection Descriptor", > + "Reserved", > + }; > + > + switch (tag_item) { > + case 0x1: /* 6.4.3.1 24-Bit Memory Range Descriptor */ > + method_test_CRS_large_size(fw, name, data, length, 9, 9, passed); > + if (!*passed) /* Too short, abort */ > + break; > + min = method_CRS_val24(&data[4]); > + max = method_CRS_val24(&data[6]); > + len = method_CRS_val16(&data[10]); > + if (max < min) { > + fwts_failed(fw, LOG_LEVEL_MEDIUM, > + "Method_CRS24BitMemRangeMaxLessThanMin", > + "%s 24-Bit Memory Range Descriptor minimum " > + "address range 0x%" PRIx64 " is greater than " > + "the maximum address range 0x%" PRIx64 ".", > + name, min, max); > + *passed = false; > + } > + if (len > max + 1 - min) { > + fwts_failed(fw, LOG_LEVEL_MEDIUM, > + "Method_CRS24BitMemRangeLengthTooLarge", > + "%s 24-Bit Memory Range Descriptor length " > + "0x%" PRIx64 " is greater than size between the " > + "the minimum and maximum address ranges " > + "0x%" PRIx64 "-0x%" PRIx64 ".", > + name, len, min, max); > + *passed = false; > + } > + break; > + case 0x2: /* 6.4.3.7 Generic Register Descriptor */ > + method_test_CRS_large_size(fw, name, data, length, 12, 12, passed); > + if (!*passed) > + break; > + switch (data[3]) { > + case 0x00 ... 0x04: > + case 0x0a: > + case 0x7f: > + /* Valid values */ > + break; > + default: > + fwts_failed(fw, LOG_LEVEL_HIGH, > + "Method_CRSGenericRegAddrSpaceIdInvalid", > + "%s Generic Register Descriptor has an invalid " > + "Address Space ID 0x%" PRIx8 ".", > + name, data[3]); > + *passed = false; > + } > + if (data[6] > 4) { > + fwts_failed(fw, LOG_LEVEL_HIGH, > + "Method_CRSGenericRegAddrSizeInvalid", > + "%s Generic Register Descriptor has an invalid " > + "Address Access Size 0x%" PRIx8 ".", > + name, data[6]); > + *passed = false; > + } > + break; > + case 0x4: /* 6.4.3.2 Vendor-Defined Descriptor */ > + method_test_CRS_large_size(fw, name, data, length, 0, 65535, passed); > + break; > + case 0x5: /* 6.4.3.3 32-Bit Memory Range Descriptor */ > + method_test_CRS_large_size(fw, name, data, length, 17, 17, passed); > + if (!*passed) > + break; > + min = method_CRS_val32(&data[4]); > + max = method_CRS_val32(&data[8]); > + len = method_CRS_val32(&data[16]); > + if (max < min) { > + fwts_failed(fw, LOG_LEVEL_MEDIUM, > + "Method_CRS32BitMemRangeMaxLessThanMin", > + "%s 32-Bit Memory Range Descriptor minimum " > + "address range 0x%" PRIx64 " is greater than " > + "the maximum address range 0x%" PRIx64 ".", > + name, min, max); > + *passed = false; > + } > + if (len > max + 1 - min) { > + fwts_failed(fw, LOG_LEVEL_MEDIUM, > + "Method_CRS32BitMemRangeLengthTooLarge", > + "%s 32-Bit Memory Range Descriptor length " > + "0x%" PRIx64 " is greater than size between the " > + "the minimum and maximum address ranges " > + "0x%" PRIx64 "-0x%" PRIx64 ".", > + name, len, min, max); > + *passed = false; > + } > + break; > + case 0x6: /* 6.4.3.4 32-Bit Fixed Memory Range Descriptor */ > + method_test_CRS_large_size(fw, name, data, length, 9, 9, passed); > + /* Not much can be checked for this descriptor */ > + break; > + case 0x7: /* 6.4.3.5.2 DWord Address Space Descriptor */ > + method_test_CRS_large_size(fw, name, data, length, 23, 65535, passed); > + if (!*passed) /* Too short, abort */ > + break; > + gra = method_CRS_val32(&data[6]); > + min = method_CRS_val32(&data[10]); > + max = method_CRS_val32(&data[14]); > + len = method_CRS_val32(&data[22]); > + > + method_test_CRS_mif_maf(fw, name, data[4], > + min, max, len, gra, > + "64BitDWordAddrSpace", > + types[0x7], passed); > + break; > + case 0x8: /* 6.4.3.5.3 Word Address Space Descriptor */ > + method_test_CRS_large_size(fw, name, data, length, 13, 65535, passed); > + if (!*passed) /* Too short, abort */ > + break; > + gra = method_CRS_val16(&data[6]); > + min = method_CRS_val16(&data[8]); > + max = method_CRS_val16(&data[10]); > + len = method_CRS_val16(&data[14]); > + > + method_test_CRS_mif_maf(fw, name, data[4], > + min, max, len, gra, > + "64BitWordAddrSpace", > + types[0x8], passed); > + break; > + case 0x9: /* 6.4.3.6 Extended Interrupt Descriptor */ > + method_test_CRS_large_size(fw, name, data, length, 6, 65535, passed); > + /* Not much can be checked for this descriptor */ > + break; > + case 0xa: /* 6.4.3.5.1 QWord Address Space Descriptor */ > + method_test_CRS_large_size(fw, name, data, length, 43, 65535, passed); > + if (!*passed) /* Too short, abort */ > + break; > + gra = method_CRS_val64(&data[6]); > + min = method_CRS_val64(&data[14]); > + max = method_CRS_val64(&data[22]); > + len = method_CRS_val64(&data[38]); > + > + method_test_CRS_mif_maf(fw, name, data[4], > + min, max, len, gra, > + "64BitQWordAddrSpace", > + types[0xa], passed); > + break; > + case 0xb: /* 6.4.3.5.4 Extended Address Space Descriptor */ > + method_test_CRS_large_size(fw, name, data, length, 53, 53, passed); > + if (!*passed) /* Too short, abort */ > + break; > + gra = method_CRS_val64(&data[8]); > + min = method_CRS_val64(&data[16]); > + max = method_CRS_val64(&data[24]); > + len = method_CRS_val64(&data[40]); > + > + method_test_CRS_mif_maf(fw, name, data[4], > + min, max, len, gra, > + "64BitExtAddrSpace", > + types[0xb], passed); > + break; > + case 0xc: /* 6.4.3.8.1 GPIO Connection Descriptor */ > + method_test_CRS_large_size(fw, name, data, length, 22, 65535, passed); > + if (!*passed) /* Too short, abort */ > + break; > + if (data[2] > 2) { Checking the spec ACPI5.0 p.335, seems that byte 4 is the GPIO Connection Type, please help to double-check that. thanks! > + fwts_failed(fw, LOG_LEVEL_MEDIUM, > + "Method_CRSGpioConnTypeInvalid", > + "%s GPIO Connection Descriptor has an invalid " > + "Connection Type 0x%" PRIx8 ".", > + name, data[2]); > + *passed = false; > + fwts_advice(fw, > + "The GPIO pin connection type is " > + "not recognised. It should be either " > + "0x00 (interrupt connection) or " > + "0x01 (I/O connection). See table " > + "6-189 in section 6.4.3.8.1 of the ACPI " > + "specification."); > + } > + if ((data[9] > 0x03) && (data[9] < 0x80)) { > + fwts_failed(fw, LOG_LEVEL_LOW, > + "Method_CRSGpioConnTypeInvalid", > + "%s GPIO Connection Descriptor has an invalid " > + "Pin Configuration Type 0x%" PRIx8 ".", > + name, data[9]); > + *passed = false; > + fwts_advice(fw, > + "The GPIO pin configuration type " > + "is not recognised. It should be one of:" > + "0x00 (default), 0x01 (pull-up), " > + "0x02 (pull-down), 0x03 (no-pull), " > + "0x80-0xff (vendor defined). See table " > + "6-189 in section 6.4.3.8.1 of the ACPI " > + "specification."); > + } > + break;
diff --git a/src/acpi/method/method.c b/src/acpi/method/method.c index 0b8dafd..76b9f9e 100644 --- a/src/acpi/method/method.c +++ b/src/acpi/method/method.c @@ -945,14 +945,674 @@ static int method_test_UID(fwts_framework *fw) "_UID", NULL, 0, method_test_UID_return, NULL); } - /* * Section 6.2 Device Configurations Objects */ +static void method_test_CRS_size( + fwts_framework *fw, + const char *name, /* full _CRS path name */ + const char *tag, /* error log tag */ + const size_t crs_length, /* size of _CRS buffer */ + const size_t hdr_length, /* size of _CRS header */ + const size_t data_length, /* length of _CRS data w/o header */ + const size_t min, /* minimum allowed _CRS data size */ + const size_t max, /* maximum allowed _CRS data size */ + bool *passed) /* pass/fail flag */ +{ + if (crs_length < data_length + hdr_length) { + fwts_failed(fw, LOG_LEVEL_HIGH, tag, + "%s Resource size is %zd bytes long but " + "the size stated in the _CRS buffer header " + "is %zd and hence is longer. The resource " + "buffer is too short.", + name, crs_length, data_length); + *passed = false; + return; + } + + if ((data_length < min) || (data_length > max)) { + if (min != max) { + fwts_failed(fw, LOG_LEVEL_MEDIUM, tag, + "%s Resource data size was %zd bytes long, " + "expected it to be between %zd and %zd bytes", + name, data_length, min, max); + *passed = false; + } else { + fwts_failed(fw, LOG_LEVEL_MEDIUM, tag, + "%s Resource data size was %zd bytes long, " + "expected it to be %zd bytes", + name, data_length, min); + *passed = false; + } + } +} + +static void method_test_CRS_small_size( + fwts_framework *fw, + const char *name, + const uint8_t *data, + const size_t crs_length, + const size_t min, + const size_t max, + bool *passed) +{ + size_t data_length = data[0] & 7; + + method_test_CRS_size(fw, name, "Method_CRSSmallResourceSize", + crs_length, 1, data_length, min, max, passed); +} + + +/* + * CRS small resource checks, simple checking + */ +static void method_test_CRS_small_resource_items( + fwts_framework *fw, + const char *name, + const uint8_t *data, + const size_t length, + bool *passed, + const char **tag) +{ + uint8_t tag_item = (data[0] >> 3) & 0xf; + + static const char *types[] = { + "Reserved", + "Reserved", + "Reserved", + "Reserved", + "IRQ Descriptor", + "DMA Descriptor", + "Start Dependent Functions Descriptor", + "End Dependent Functions Descriptor", + "I/O Port Descriptor", + "Fixed Location I/O Port Descriptor", + "Fixed DMA Descriptor", + "Reserved", + "Reserved", + "Reserved", + "Vendor Defined Descriptor", + "End Tag Descriptor" + }; + + switch (tag_item) { + case 0x4: /* 6.4.2.1 IRQ Descriptor */ + method_test_CRS_small_size(fw, name, data, length, 2, 3, passed); + break; + case 0x5: /* 6.4.2.2 DMA Descriptor */ + method_test_CRS_small_size(fw, name, data, length, 2, 2, passed); + if (!*passed) /* Too short, abort */ + break; + if ((data[2] & 3) == 3) { + fwts_failed(fw, LOG_LEVEL_HIGH, + "Method_CRSDmaDescriptor", + "%s DMA transfer type preference is 0x%" PRIx8 + " which is reserved and invalid. See " + "Section 6.4.2.2 of the ACPI specification.", + name, data[2] & 3); + *passed = false; + } + break; + case 0x6: /* 6.4.2.3 Start Dependent Functions Descriptor */ + method_test_CRS_small_size(fw, name, data, length, 0, 1, passed); + break; + case 0x7: /* 6.4.2.4 End Dependent Functions Descriptor */ + method_test_CRS_small_size(fw, name, data, length, 0, 0, passed); + break; + case 0x8: /* 6.4.2.5 I/O Port Descriptor */ + method_test_CRS_small_size(fw, name, data, length, 7, 7, passed); + if (!*passed) /* Too short, abort */ + break; + if (data[1] & 0xfe) { + fwts_failed(fw, LOG_LEVEL_LOW, + "Method_CRSIoPortInfoReservedNonZero", + "%s I/O Port Descriptor Information field " + "has reserved bits that are non-zero, got " + "0x%" PRIx8 " and expected 0 or 1 for this " + "field. ", name, data[1]); + *passed = false; + } + if (((data[1] & 1) == 0) && (data[3] > 3)) { + fwts_failed(fw, LOG_LEVEL_LOW, + "Method_CRSIoPortInfoMinBase10BitAddr", + "%s I/O Port Descriptor range minimum " + "base address is more than 10 bits however " + "the Information field indicates that only " + "a 10 bit address is being used.", name); + *passed = false; + } + if (((data[1] & 1) == 0) && (data[5] > 3)) { + fwts_failed(fw, LOG_LEVEL_LOW, + "Method_CRSIoPortInfoMinBase10BitAddr", + "%s I/O Port Descriptor range maximum " + "base address is more than 10 bits however " + "the Information field indicates that only " + "a 10 bit address is being used.", name); + *passed = false; + } + break; + case 0x9: /* 6.4.2.6 Fixed Location I/O Port Descriptor */ + method_test_CRS_small_size(fw, name, data, length, 3, 3, passed); + break; + case 0xa: /* 6.4.2.7 Fixed DMA Descriptor */ + method_test_CRS_small_size(fw, name, data, length, 5, 5, passed); + if (!*passed) /* Too short, abort */ + break; + if (data[5] > 5) { + fwts_failed(fw, LOG_LEVEL_HIGH, + "Method_CRSFixedDmaTransferWidth", + "%s DMA transfer width is 0x%" PRIx8 + " which is reserved and invalid. See " + "Section 6.4.2.7 of the ACPI specification.", + name, data[5]); + *passed = false; + } + break; + case 0xe: /* 6.4.2.8 Vendor-Defined Descriptor */ + method_test_CRS_small_size(fw, name, data, length, 1, 7, passed); + break; + case 0xf: /* 6.4.2.9 End Tag */ + method_test_CRS_small_size(fw, name, data, length, 1, 1, passed); + break; + default: + fwts_failed(fw, LOG_LEVEL_LOW, + "Method_CRSUnkownSmallResourceItem", + "%s tag bits 6:3 is an undefined " + "small tag item name, value 0x%" PRIx8 ".", + name, tag_item); + fwts_advice(fw, + "A small resource data type tag (byte 0, " + "bits 6:3 of the _CRS buffer) contains " + "an undefined small tag item 'name'. " + "The _CRS buffer is therefore undefined " + "and can't be used. See section " + "'6.4.2 Small Resource Data Type' of the ACPI " + "specification, and also table 6-161."); + *passed = false; + break; + } + + *tag = types[tag_item]; +} + +static void method_test_CRS_large_size( + fwts_framework *fw, + const char *name, + const uint8_t *data, + const size_t crs_length, + const size_t min, + const size_t max, + bool *passed) +{ + size_t data_length; + + /* Small _CRS resources have a 3 byte header */ + if (crs_length < 3) { + fwts_failed(fw, LOG_LEVEL_MEDIUM, "Method_CRSBufferTooSmall", + "%s should return a buffer of at least three bytes in length.", name); + *passed = false; + return; + } + + data_length = (size_t)data[1] + ((size_t)data[2] << 8); + + method_test_CRS_size(fw, name, "Method_CRSLargeResourceSize", + crs_length, 3, data_length, min, max, passed); +} + +/* + * Some CRS value fetching helper functions. We handle all the + * addresses and lengths in 64 bits to make life easier + */ +static uint64_t method_CRS_val64(const uint8_t *data) +{ + uint64_t val = + ((uint64_t)data[7] << 56) | ((uint64_t)data[6] << 48) | + ((uint64_t)data[5] << 40) | ((uint64_t)data[0] << 32) | + ((uint64_t)data[3] << 24) | ((uint64_t)data[2] << 16) | + ((uint64_t)data[1] << 8) | (uint64_t)data[0]; + + return val; +} + +static uint64_t method_CRS_val32(const uint8_t *data) +{ + uint64_t val = + ((uint64_t)data[3] << 24) | ((uint64_t)data[2] << 16) | + ((uint64_t)data[1] << 8) | (uint64_t)data[0]; + + return val; +} + +static uint64_t method_CRS_val24(const uint8_t *data) +{ + /* 24 bit values assume lower 8 bits are zero */ + uint64_t val = + ((uint64_t)data[1] << 16) | ((uint64_t)data[0] << 8); + + return val; +} + +static uint64_t method_CRS_val16(const uint8_t *data) +{ + uint64_t val = + ((uint64_t)data[1] << 8) | (uint64_t)data[0]; + + return val; +} + +/* + * Sanity check addresses according to table 6-179 of ACPI spec + */ +static void method_test_CRS_mif_maf( + fwts_framework *fw, + const char *name, /* Full _CRS path name */ + const uint8_t flag, /* _MIF _MAF flag field */ + const uint64_t min, /* Min address */ + const uint64_t max, /* Max address */ + const uint64_t len, /* Range length */ + const uint64_t granularity, /* Address granularity */ + const char *tag, /* failed error tag */ + const char *type, /* Resource type */ + bool *passed) +{ + char tmp[128]; + uint8_t mif = (flag >> 2) & 1; + uint8_t maf = (flag >> 3) & 1; + + static char *mif_maf_advice = + "See section '6.4.3.5 Address Space Resource Descriptors' " + "table 6-179 of the ACPI specification for more details " + "about how the _MIF, _MAF and memory range and granularity " + "rules apply. Typically the kernel does not care about these " + "being correct, so this is a minor issue."; + + /* Table 6-179 Valid combination of Address Space Descriptors fields */ + if (len == 0) { + if ((mif == 1) && (maf == 1)) { + snprintf(tmp, sizeof(tmp), "Method_CRS%sMifMafBothOne", tag); + fwts_failed(fw, LOG_LEVEL_MEDIUM, + tmp, + "%s %s _MIF and _MAF flags are both " + "set to one which is invalid when " + "the length field is 0.", + name, type); + fwts_advice(fw, "%s", mif_maf_advice); + *passed = false; + } + if ((mif == 1) && (min % (granularity + 1) != 0)) { + snprintf(tmp, sizeof(tmp), "Method_CRS%sMinNotMultipleOfGran", tag); + fwts_failed(fw, LOG_LEVEL_MEDIUM, + tmp, + "%s %s _MIN address is not a multiple " + "of the granularity when _MIF is 1.", + name, type); + fwts_advice(fw, "%s", mif_maf_advice); + *passed = false; + } + if ((maf == 1) && (max % (granularity - 1) != 0)) { + snprintf(tmp, sizeof(tmp), "Method_CRS%sMaxNotMultipleOfGran", tag); + fwts_failed(fw, LOG_LEVEL_MEDIUM, + tmp, + "%s %s _MAX address is not a multiple " + "of the granularity when _MAF is 1.", + name, type); + fwts_advice(fw, "%s", mif_maf_advice); + *passed = false; + } + } else { + if ((mif == 0) && (maf == 0) && + (len % (granularity + 1) != 0)) { + snprintf(tmp, sizeof(tmp), "Method_CRS%sLenNotMultipleOfGran", tag); + fwts_failed(fw, LOG_LEVEL_MEDIUM, + tmp, + "%s %s length is not a multiple " + "of the granularity when _MIF " + "and _MIF are 0.", + name, type); + fwts_advice(fw, "%s", mif_maf_advice); + *passed = false; + } + if (((mif == 0) && (maf == 1)) || + ((mif == 1) && (maf == 0))) { + snprintf(tmp, sizeof(tmp), "Method_CRS%sMifMafInvalid", tag); + fwts_failed(fw, LOG_LEVEL_MEDIUM, + tmp, + "%s %s _MIF and _MAF flags are either " + "0 and 1 or 1 and 0 which is invalid when " + "the length field is non-zero.", + name, type); + fwts_advice(fw, "%s", mif_maf_advice); + *passed = false; + } + if ((mif == 1) && (maf == 1)) { + if (granularity != 0) { + snprintf(tmp, sizeof(tmp), "Method_CRS%sGranularityNotZero", tag); + fwts_failed(fw, LOG_LEVEL_MEDIUM, + tmp, + "%s %s granularity 0x%" PRIx64 + " is not zero as expected when " + "_MIF and _MAF are both 1.", + name, type, granularity); + fwts_advice(fw, "%s", mif_maf_advice); + *passed = false; + } + if (min > max) { + snprintf(tmp, sizeof(tmp), "Method_CRS%sMaxLessThanMin", tag); + fwts_failed(fw, LOG_LEVEL_MEDIUM, + tmp, + "%s %s minimum address range 0x%" PRIx64 + " is greater than the maximum address " + "range 0x%" PRIx64 ".", + name, type, min, max); + fwts_advice(fw, "%s", mif_maf_advice); + *passed = false; + } + if (max - min + 1 != len) { + snprintf(tmp, sizeof(tmp), "Method_CRS%sLengthInvalid", tag); + fwts_failed(fw, LOG_LEVEL_MEDIUM, + tmp, + "%s %s length 0x%" PRIx64 + " does not match the difference between " + "the minimum and maximum address ranges " + "0x%" PRIx64 "-0x%" PRIx64 ".", + name, type, len, min, max); + fwts_advice(fw, "%s", mif_maf_advice); + *passed = false; + } + } + } +} + +/* + * CRS large resource checks, simple checking + */ +static void method_test_CRS_large_resource_items( + fwts_framework *fw, + const char *name, + const uint8_t *data, + const uint64_t length, + bool *passed, + const char **tag) +{ + uint64_t min, max, len, gra; + uint8_t tag_item = data[0] & 0x7f; + + static const char *types[] = { + "Reserved", + "24-bit Memory Range Descriptor", + "Generic Register Descriptor", + "Reserved", + "Vendor Defined Descriptor", + "32-bit Memory Range Descriptor", + "32-bit Fixed Location Memory Range Descriptor", + "DWORD Address Space Descriptor", + "WORD Address Space Descriptor", + "Extended IRQ Descriptor", + "QWORD Address Space Descriptor", + "Extended Addresss Space Descriptor", + "GPIO Connection Descriptor", + "Reserved", + "Generic Serial Bus Connection Descriptor", + "Reserved", + }; + + switch (tag_item) { + case 0x1: /* 6.4.3.1 24-Bit Memory Range Descriptor */ + method_test_CRS_large_size(fw, name, data, length, 9, 9, passed); + if (!*passed) /* Too short, abort */ + break; + min = method_CRS_val24(&data[4]); + max = method_CRS_val24(&data[6]); + len = method_CRS_val16(&data[10]); + if (max < min) { + fwts_failed(fw, LOG_LEVEL_MEDIUM, + "Method_CRS24BitMemRangeMaxLessThanMin", + "%s 24-Bit Memory Range Descriptor minimum " + "address range 0x%" PRIx64 " is greater than " + "the maximum address range 0x%" PRIx64 ".", + name, min, max); + *passed = false; + } + if (len > max + 1 - min) { + fwts_failed(fw, LOG_LEVEL_MEDIUM, + "Method_CRS24BitMemRangeLengthTooLarge", + "%s 24-Bit Memory Range Descriptor length " + "0x%" PRIx64 " is greater than size between the " + "the minimum and maximum address ranges " + "0x%" PRIx64 "-0x%" PRIx64 ".", + name, len, min, max); + *passed = false; + } + break; + case 0x2: /* 6.4.3.7 Generic Register Descriptor */ + method_test_CRS_large_size(fw, name, data, length, 12, 12, passed); + if (!*passed) + break; + switch (data[3]) { + case 0x00 ... 0x04: + case 0x0a: + case 0x7f: + /* Valid values */ + break; + default: + fwts_failed(fw, LOG_LEVEL_HIGH, + "Method_CRSGenericRegAddrSpaceIdInvalid", + "%s Generic Register Descriptor has an invalid " + "Address Space ID 0x%" PRIx8 ".", + name, data[3]); + *passed = false; + } + if (data[6] > 4) { + fwts_failed(fw, LOG_LEVEL_HIGH, + "Method_CRSGenericRegAddrSizeInvalid", + "%s Generic Register Descriptor has an invalid " + "Address Access Size 0x%" PRIx8 ".", + name, data[6]); + *passed = false; + } + break; + case 0x4: /* 6.4.3.2 Vendor-Defined Descriptor */ + method_test_CRS_large_size(fw, name, data, length, 0, 65535, passed); + break; + case 0x5: /* 6.4.3.3 32-Bit Memory Range Descriptor */ + method_test_CRS_large_size(fw, name, data, length, 17, 17, passed); + if (!*passed) + break; + min = method_CRS_val32(&data[4]); + max = method_CRS_val32(&data[8]); + len = method_CRS_val32(&data[16]); + if (max < min) { + fwts_failed(fw, LOG_LEVEL_MEDIUM, + "Method_CRS32BitMemRangeMaxLessThanMin", + "%s 32-Bit Memory Range Descriptor minimum " + "address range 0x%" PRIx64 " is greater than " + "the maximum address range 0x%" PRIx64 ".", + name, min, max); + *passed = false; + } + if (len > max + 1 - min) { + fwts_failed(fw, LOG_LEVEL_MEDIUM, + "Method_CRS32BitMemRangeLengthTooLarge", + "%s 32-Bit Memory Range Descriptor length " + "0x%" PRIx64 " is greater than size between the " + "the minimum and maximum address ranges " + "0x%" PRIx64 "-0x%" PRIx64 ".", + name, len, min, max); + *passed = false; + } + break; + case 0x6: /* 6.4.3.4 32-Bit Fixed Memory Range Descriptor */ + method_test_CRS_large_size(fw, name, data, length, 9, 9, passed); + /* Not much can be checked for this descriptor */ + break; + case 0x7: /* 6.4.3.5.2 DWord Address Space Descriptor */ + method_test_CRS_large_size(fw, name, data, length, 23, 65535, passed); + if (!*passed) /* Too short, abort */ + break; + gra = method_CRS_val32(&data[6]); + min = method_CRS_val32(&data[10]); + max = method_CRS_val32(&data[14]); + len = method_CRS_val32(&data[22]); + + method_test_CRS_mif_maf(fw, name, data[4], + min, max, len, gra, + "64BitDWordAddrSpace", + types[0x7], passed); + break; + case 0x8: /* 6.4.3.5.3 Word Address Space Descriptor */ + method_test_CRS_large_size(fw, name, data, length, 13, 65535, passed); + if (!*passed) /* Too short, abort */ + break; + gra = method_CRS_val16(&data[6]); + min = method_CRS_val16(&data[8]); + max = method_CRS_val16(&data[10]); + len = method_CRS_val16(&data[14]); + + method_test_CRS_mif_maf(fw, name, data[4], + min, max, len, gra, + "64BitWordAddrSpace", + types[0x8], passed); + break; + case 0x9: /* 6.4.3.6 Extended Interrupt Descriptor */ + method_test_CRS_large_size(fw, name, data, length, 6, 65535, passed); + /* Not much can be checked for this descriptor */ + break; + case 0xa: /* 6.4.3.5.1 QWord Address Space Descriptor */ + method_test_CRS_large_size(fw, name, data, length, 43, 65535, passed); + if (!*passed) /* Too short, abort */ + break; + gra = method_CRS_val64(&data[6]); + min = method_CRS_val64(&data[14]); + max = method_CRS_val64(&data[22]); + len = method_CRS_val64(&data[38]); + + method_test_CRS_mif_maf(fw, name, data[4], + min, max, len, gra, + "64BitQWordAddrSpace", + types[0xa], passed); + break; + case 0xb: /* 6.4.3.5.4 Extended Address Space Descriptor */ + method_test_CRS_large_size(fw, name, data, length, 53, 53, passed); + if (!*passed) /* Too short, abort */ + break; + gra = method_CRS_val64(&data[8]); + min = method_CRS_val64(&data[16]); + max = method_CRS_val64(&data[24]); + len = method_CRS_val64(&data[40]); + + method_test_CRS_mif_maf(fw, name, data[4], + min, max, len, gra, + "64BitExtAddrSpace", + types[0xb], passed); + break; + case 0xc: /* 6.4.3.8.1 GPIO Connection Descriptor */ + method_test_CRS_large_size(fw, name, data, length, 22, 65535, passed); + if (!*passed) /* Too short, abort */ + break; + if (data[2] > 2) { + fwts_failed(fw, LOG_LEVEL_MEDIUM, + "Method_CRSGpioConnTypeInvalid", + "%s GPIO Connection Descriptor has an invalid " + "Connection Type 0x%" PRIx8 ".", + name, data[2]); + *passed = false; + fwts_advice(fw, + "The GPIO pin connection type is " + "not recognised. It should be either " + "0x00 (interrupt connection) or " + "0x01 (I/O connection). See table " + "6-189 in section 6.4.3.8.1 of the ACPI " + "specification."); + } + if ((data[9] > 0x03) && (data[9] < 0x80)) { + fwts_failed(fw, LOG_LEVEL_LOW, + "Method_CRSGpioConnTypeInvalid", + "%s GPIO Connection Descriptor has an invalid " + "Pin Configuration Type 0x%" PRIx8 ".", + name, data[9]); + *passed = false; + fwts_advice(fw, + "The GPIO pin configuration type " + "is not recognised. It should be one of:" + "0x00 (default), 0x01 (pull-up), " + "0x02 (pull-down), 0x03 (no-pull), " + "0x80-0xff (vendor defined). See table " + "6-189 in section 6.4.3.8.1 of the ACPI " + "specification."); + } + break; + case 0xe: /* 6.4.3.8.2 Serial Bus Connection Descriptors */ + method_test_CRS_large_size(fw, name, data, length, 11, 65535, passed); + /* Don't care */ + break; + default: + fwts_failed(fw, LOG_LEVEL_LOW, + "Method_CRSUnkownLargeResourceItem", + "%s tag bits 6:0 is an undefined " + "large tag item name, value 0x%" PRIx8 ".", + name, tag_item); + fwts_advice(fw, + "A large resource data type tag (byte 0 of the " + "_CRS buffer) contains an undefined large tag " + "item 'name'. The _CRS buffer is therefore " + "undefined and can't be used. See section " + "'6.4.3 Large Resource Data Type' of the ACPI " + "specification, and also table 6-173."); + *passed = false; + break; + } + + *tag = types[tag_item < 16 ? tag_item : 0]; +} + +static void method_test_CRS_return( + fwts_framework *fw, + char *name, + ACPI_BUFFER *buf, + ACPI_OBJECT *obj, + void *private) +{ + uint8_t *data; + bool passed = true; + const char *tag = "Unknown"; + + FWTS_UNUSED(private); + + if (method_check_type(fw, name, buf, ACPI_TYPE_BUFFER) != FWTS_OK) + return; + if (obj->Buffer.Pointer == NULL) { + fwts_failed(fw, LOG_LEVEL_MEDIUM, "Method_CRSNullBuffer", + "%s returned a NULL buffer pointer.", name); + return; + } + if (obj->Buffer.Length < 1) { + fwts_failed(fw, LOG_LEVEL_MEDIUM, "Method_CRSBufferTooSmall", + "%s should return a buffer of at least one byte in length.", name); + return; + } + + data = (uint8_t*)obj->Buffer.Pointer; + + if (data[0] & 128) + method_test_CRS_large_resource_items(fw, name, data, obj->Buffer.Length, &passed, &tag); + else + method_test_CRS_small_resource_items(fw, name, data, obj->Buffer.Length, &passed, &tag); + + if (passed) + fwts_passed(fw, "%s (%s) looks sane.", name, tag); + else + fwts_tag_failed(fw, FWTS_TAG_ACPI_METHOD_RETURN); +} + +/* + * method_test_integer_return + * check if an integer object was returned + */ static int method_test_CRS(fwts_framework *fw) { return method_evaluate_method(fw, METHOD_MANDITORY, - "_CRS", NULL, 0, method_test_buffer_return, NULL); + "_CRS", NULL, 0, method_test_CRS_return, NULL); } static int method_test_DMA(fwts_framework *fw)