ACPI: Validate XSDT, use RSDT if XSDT fails
authorZhao Yakui <yakui.zhao@intel.com>
Fri, 24 Aug 2007 08:18:16 +0000 (16:18 +0800)
committerLen Brown <len.brown@intel.com>
Fri, 24 Aug 2007 22:46:25 +0000 (18:46 -0400)
ACPI 1.0 used an RSDT with 32-bit physical addresses.
ACPI 2.0 adds an XSDT with 32-bit physical addresses.
An ACPI 2.0 aware OS is supposed to use the XSDT
(when present) instead of the RSDT.

However, several systems have failed because the XSDT
contains NULL entries -- while it is missing pointers
to needed tables, such as SSDTs.

When we find an XSDT with NULL entries, discard it
and use the ACPI 1.0 RSDT instead.

http://bugzilla.kernel.org/show_bug.cgi?id=8630

Signed-off-by: Zhao Yakui <yakui.zhao@intel.com>
Signed-off-by: Len Brown <len.brown@intel.com>
drivers/acpi/tables/tbutils.c

index 1da64b4..8cc9492 100644 (file)
@@ -51,6 +51,65 @@ ACPI_MODULE_NAME("tbutils")
 static acpi_physical_address
 acpi_tb_get_root_table_entry(u8 * table_entry,
                             acpi_native_uint table_entry_size);
+/*******************************************************************************
+ *
+ * FUNCTION:    acpi_tb_check_xsdt
+ *
+ * PARAMETERS:  address                    - Pointer to the XSDT
+ *
+ * RETURN:      status
+ *             AE_OK - XSDT is okay
+ *             AE_NO_MEMORY - can't map XSDT
+ *             AE_INVALID_TABLE_LENGTH - invalid table length
+ *             AE_NULL_ENTRY - XSDT has NULL entry
+ *
+ * DESCRIPTION: validate XSDT
+******************************************************************************/
+
+static acpi_status
+acpi_tb_check_xsdt(acpi_physical_address address)
+{
+       struct acpi_table_header *table;
+       u32 length;
+       u64 xsdt_entry_address;
+       u8 *table_entry;
+       u32 table_count;
+       int i;
+
+       table = acpi_os_map_memory(address, sizeof(struct acpi_table_header));
+       if (!table)
+               return AE_NO_MEMORY;
+
+       length = table->length;
+       acpi_os_unmap_memory(table, sizeof(struct acpi_table_header));
+       if (length < sizeof(struct acpi_table_header))
+               return AE_INVALID_TABLE_LENGTH;
+
+       table = acpi_os_map_memory(address, length);
+       if (!table)
+               return AE_NO_MEMORY;
+
+       /* Calculate the number of tables described in XSDT */
+       table_count =
+               (u32) ((table->length -
+               sizeof(struct acpi_table_header)) / sizeof(u64));
+       table_entry =
+               ACPI_CAST_PTR(u8, table) + sizeof(struct acpi_table_header);
+       for (i = 0; i < table_count; i++) {
+               ACPI_MOVE_64_TO_64(&xsdt_entry_address, table_entry);
+               if (!xsdt_entry_address) {
+                       /* XSDT has NULL entry */
+                       break;
+               }
+               table_entry += sizeof(u64);
+       }
+       acpi_os_unmap_memory(table, length);
+
+       if (i < table_count)
+               return AE_NULL_ENTRY;
+       else
+               return AE_OK;
+}
 
 /*******************************************************************************
  *
@@ -341,6 +400,7 @@ acpi_tb_parse_root_table(acpi_physical_address rsdp_address, u8 flags)
        u32 table_count;
        struct acpi_table_header *table;
        acpi_physical_address address;
+       acpi_physical_address rsdt_address;
        u32 length;
        u8 *table_entry;
        acpi_status status;
@@ -369,6 +429,8 @@ acpi_tb_parse_root_table(acpi_physical_address rsdp_address, u8 flags)
                 */
                address = (acpi_physical_address) rsdp->xsdt_physical_address;
                table_entry_size = sizeof(u64);
+               rsdt_address = (acpi_physical_address)
+                                       rsdp->rsdt_physical_address;
        } else {
                /* Root table is an RSDT (32-bit physical addresses) */
 
@@ -382,6 +444,15 @@ acpi_tb_parse_root_table(acpi_physical_address rsdp_address, u8 flags)
         */
        acpi_os_unmap_memory(rsdp, sizeof(struct acpi_table_rsdp));
 
+       if (table_entry_size == sizeof(u64)) {
+               if (acpi_tb_check_xsdt(address) == AE_NULL_ENTRY) {
+                       /* XSDT has NULL entry, RSDT is used */
+                       address = rsdt_address;
+                       table_entry_size = sizeof(u32);
+                       ACPI_WARNING((AE_INFO, "BIOS XSDT has NULL entry,"
+                                       "using RSDT"));
+               }
+       }
        /* Map the RSDT/XSDT table header to get the full table length */
 
        table = acpi_os_map_memory(address, sizeof(struct acpi_table_header));