- ACPI: Implement overriding of arbitrary ACPI tables via initrd
authorThomas Renninger <trenn@suse.de>
Tue, 20 Sep 2011 16:09:04 +0000 (18:09 +0200)
committerThomas Renninger <trenn@suse.de>
Tue, 20 Sep 2011 16:09:04 +0000 (18:09 +0200)
  (none).
- ACPICA: Fix wrongly mapped acpi table header when overriding
  via initrd (none).
- ACPICA: Introduce acpi_os_phys_table_override function (none).
- x86: allow NVS can be accessed by driver (none).
- Update config files:
CONFIG_ACPI_INITRD_TABLE_OVERRIDE=y
on i386 and x86_64 flavors

suse-commit: 34bb1954ece98383dfe96bb3792be85133212849

Documentation/acpi/initrd_table_override.txt [new file with mode: 0644]
arch/x86/kernel/e820.c
arch/x86/kernel/setup.c
arch/x86/mm/init.c
drivers/acpi/Kconfig
drivers/acpi/acpica/tbinstal.c
drivers/acpi/acpica/tbutils.c
drivers/acpi/osl.c
include/acpi/acpiosxf.h
include/linux/acpi.h
include/linux/initrd.h

diff --git a/Documentation/acpi/initrd_table_override.txt b/Documentation/acpi/initrd_table_override.txt
new file mode 100644 (file)
index 0000000..7b29d5f
--- /dev/null
@@ -0,0 +1,110 @@
+Overriding ACPI tables via initrd
+=================================
+
+1) Introduction (What is this about)
+2) What is this for
+3) How does it work
+4) References (Where to retrieve userspace tools)
+
+1) What is this about
+---------------------
+
+If ACPI_INITRD_TABLE_OVERRIDE compile option is true, it is possible to
+override nearly any ACPI table provided by the BIOS with an instrumented,
+modified one.
+
+Up to 10 arbitrary ACPI tables can be passed.
+For a full list of ACPI tables that can be overridden, take a look at
+the char *table_sigs[MAX_ACPI_SIGNATURE]; definition in drivers/acpi/osl.c
+All ACPI tables iasl (Intel's ACPI compiler and disassembler) knows should
+be overridable, except:
+   - ACPI_SIG_RSDP (has a signature of 6 bytes)
+   - ACPI_SIG_FACS (does not have an ordinary ACPI table header)
+Both could get implemented as well.
+
+
+2) What is this for
+-------------------
+
+Please keep in mind that this is a debug option.
+ACPI tables should not get overridden for productive use.
+If BIOS ACPI tables are overridden the kernel will get tainted with the
+TAINT_OVERRIDDEN_ACPI_TABLE flag.
+Complain to your platform/BIOS vendor if you find a bug which is that sever
+that a workaround is not accepted in the Linus kernel.
+
+Still, it can and should be enabled in any kernel, because:
+  - There is no functional change with not instrumented initrds
+  - It provides a powerful feature to easily debug and test ACPI BIOS table
+    compatibility with the Linux kernel.
+
+Until now it was only possible to override the DSDT by compiling it into
+the kernel. This is a nightmare when trying to work on ACPI related bugs
+and a lot bugs got stuck because of that.
+Even for people with enough kernel knowledge, building a kernel to try out
+things is very time consuming. Also people may have to browse and modify the
+ACPI interpreter code to find a possible BIOS bug. With this feature, people
+can correct the ACPI tables and try out quickly whether this is the root cause
+that needs to get addressed in the kernel.
+
+This could even ease up testing for BIOS providers who could flush their BIOS
+to test, but overriding table via initrd is much easier and quicker.
+For example one could prepare different initrds overriding NUMA tables with
+different affinity settings. Set up a script, let the machine reboot and
+run tests over night and one can get a picture how these settings influence
+the Linux kernel and which values are best.
+
+People can instrument the dynamic ACPI (ASL) code (for example with debug
+statements showing up in syslog when the ACPI code is processed, etc.),
+to better understand BIOS to OS interfaces, to hunt down ACPI BIOS code related
+bugs quickly or to easier develop ACPI based drivers.
+
+Intstrumenting ACPI code in SSDTs is now much easier. Before, one had to copy
+all SSDTs into the DSDT to compile it into the kernel for testing
+(because only DSDT could get overridden). That's what the acpi_no_auto_ssdt
+boot param is for: the BIOS provided SSDTs are ignored and all have to get
+copied into the DSDT, complicated and time consuming.
+
+Much more use cases, depending on which ACPI parts you are working on...
+
+
+3) How does it work
+-------------------
+
+# Extract the machine's ACPI tables:
+acpidump >acpidump
+acpixtract -a acpidump
+# Disassemble, modify and recompile them:
+iasl -d *.dat
+# For example add this statement into a _PRT (PCI Routing Table) function
+# of the DSDT:
+Store("Hello World", debug)
+iasl -sa *.dsl
+# glue them together with the initrd. ACPI tables go first, original initrd
+# goes on top:
+cat TBL1.dat >>instrumented_initrd
+cat TBL2.dat >>instrumented_initrd
+cat TBL3.dat >>instrumented_initrd
+cat /boot/initrd >>instrumented_initrd
+# reboot with increased acpi debug level, e.g. boot params:
+acpi.debug_level=0x2 acpi.debug_layer=0xFFFFFFFF
+# and check your syslog:
+[    1.268089] ACPI: PCI Interrupt Routing Table [\_SB_.PCI0._PRT]
+[    1.272091] [ACPI Debug]  String [0x0B] "HELLO WORLD"
+
+iasl is able to disassemble and recompile quite a lot different,
+also static ACPI tables.
+
+4) Where to retrieve userspace tools
+------------------------------------
+
+iasl and acpixtract are part of Intel's ACPICA project:
+http://acpica.org/
+and should be packaged by distributions (for example in the acpica package
+on SUSE).
+
+acpidump can be found in Len Browns pmtools:
+ftp://kernel.org/pub/linux/kernel/people/lenb/acpi/utils/pmtools/acpidump
+This tool is also part of the acpica package on SUSE.
+Alternatively used ACPI tables can be retrieved via sysfs in latest kernels:
+/sys/firmware/acpi/tables
index 3e2ef84..611560f 100644 (file)
@@ -963,7 +963,8 @@ void __init e820_reserve_resources(void)
                 * pcibios_resource_survey()
                 */
                if (e820.map[i].type != E820_RESERVED || res->start < (1ULL<<20)) {
-                       res->flags |= IORESOURCE_BUSY;
+                       if (e820.map[i].type != E820_NVS)
+                               res->flags |= IORESOURCE_BUSY;
                        insert_resource(&iomem_resource, res);
                }
                res++;
index afaf384..9fe9aa5 100644 (file)
@@ -411,12 +411,20 @@ static void __init reserve_initrd(void)
                 */
                initrd_start = ramdisk_image + PAGE_OFFSET;
                initrd_end = initrd_start + ramdisk_size;
-               return;
+       } else {
+               relocate_initrd();
+               memblock_x86_free_range(ramdisk_image, ramdisk_end);
        }
-
-       relocate_initrd();
-
-       memblock_x86_free_range(ramdisk_image, ramdisk_end);
+#ifdef CONFIG_ACPI_INITRD_TABLE_OVERRIDE
+       acpi_initrd_offset = acpi_initrd_table_override((void *)initrd_start,
+                                                       (void *)initrd_end);
+       if (!acpi_initrd_offset)
+               return;
+       printk(KERN_INFO "Found acpi tables of size: %lu at 0x%lx\n",
+              acpi_initrd_offset, initrd_start);
+       initrd_start += acpi_initrd_offset;
+       return;
+#endif
 }
 #else
 static void __init reserve_initrd(void)
index 3032644..8843eca 100644 (file)
@@ -390,6 +390,12 @@ void free_initrd_mem(unsigned long start, unsigned long end)
         *   - relocate_initrd()
         * So here We can do PAGE_ALIGN() safely to get partial page to be freed
         */
+#ifdef CONFIG_ACPI_INITRD_TABLE_OVERRIDE
+       if (acpi_initrd_offset)
+               free_init_pages("initrd memory", start - acpi_initrd_offset,
+                               PAGE_ALIGN(end));
+       else
+#endif
        free_init_pages("initrd memory", start, PAGE_ALIGN(end));
 }
 #endif
index 7248a4f..42c0fea 100644 (file)
@@ -261,6 +261,16 @@ config ACPI_CUSTOM_DSDT
        bool
        default ACPI_CUSTOM_DSDT_FILE != ""
 
+config ACPI_INITRD_TABLE_OVERRIDE
+       bool
+       depends on X86
+       default y
+       help
+         This option provides functionality to override arbitrary ACPI tables
+         via initrd. No functional change if no ACPI tables are glued to the
+         initrd, therefore it's safe to say Y.
+         See Documentation/acpi/initrd_table_override.txt for details
+
 config ACPI_BLACKLIST_YEAR
        int "Disable ACPI for systems before Jan 1st this year" if X86_32
        default 0
index 62365f6..b9b9d2a 100644 (file)
@@ -242,6 +242,21 @@ acpi_tb_add_table(struct acpi_table_desc *table_desc, u32 *table_index)
                table_desc->pointer = override_table;
                table_desc->length = override_table->length;
                table_desc->flags = ACPI_TABLE_ORIGIN_OVERRIDE;
+       } else {
+               acpi_physical_address address = 0;
+               u32 table_len = 0;
+               status = acpi_os_phys_table_override(table_desc->pointer,
+                                                    &address, &table_len);
+               if (ACPI_SUCCESS(status) && table_len && address) {
+                       ACPI_INFO((AE_INFO, "%4.4s @ 0x%p "
+                                  "Phys table override, replaced with:",
+                                  table_desc->pointer->signature,
+                                  ACPI_CAST_PTR(void, table_desc->address)));
+                       table_desc->address = address;
+                       table_desc->pointer = acpi_os_map_memory(address,
+                                                                table_len);
+                       table_desc->length = table_len;
+               }
        }
 
        /* Add the table to the global root table list */
index 0f2d395..2e797d9 100644 (file)
@@ -499,8 +499,24 @@ acpi_tb_install_table(acpi_physical_address address,
                table_to_install = override_table;
                flags = ACPI_TABLE_ORIGIN_OVERRIDE;
        } else {
-               table_to_install = mapped_table;
+               u32 table_len = 0;
+               acpi_physical_address tmp_addr = 0;
+
+               status = acpi_os_phys_table_override(mapped_table,
+                                                    &tmp_addr, &table_len);
+               if (ACPI_SUCCESS(status) && table_len && tmp_addr) {
+                       ACPI_INFO((AE_INFO, "%4.4s @ 0x%p "
+                                  "Phys table override, replaced with:",
+                                  mapped_table->signature,
+                                  ACPI_CAST_PTR(void, address)));
+                       acpi_os_unmap_memory(mapped_table,
+                                            sizeof(struct acpi_table_header));
+                       address = tmp_addr;
+                       mapped_table = acpi_os_map_memory(address,
+                                         sizeof(struct acpi_table_header));
+               }
                flags = ACPI_TABLE_ORIGIN_MAPPED;
+               table_to_install = mapped_table;
        }
 
        /* Initialize the table entry */
index fa32f58..d901752 100644 (file)
 #include <linux/list.h>
 #include <linux/jiffies.h>
 #include <linux/semaphore.h>
+#include <linux/memblock.h>
 
 #include <asm/io.h>
 #include <asm/uaccess.h>
+#include <asm/e820.h>
 
 #include <acpi/acpi.h>
 #include <acpi/acpi_bus.h>
@@ -499,6 +501,107 @@ acpi_os_predefined_override(const struct acpi_predefined_names *init_val,
        return AE_OK;
 }
 
+#ifdef CONFIG_ACPI_INITRD_TABLE_OVERRIDE
+#define ACPI_OVERRIDE_TABLES 10
+
+static unsigned long acpi_table_override_offset[ACPI_OVERRIDE_TABLES];
+static u64 acpi_tables_inram;
+
+unsigned long __initdata acpi_initrd_offset;
+
+/* Copied from acpica/tbutils.c:acpi_tb_checksum() */
+u8 __init acpi_table_checksum(u8 *buffer, u32 length)
+{
+       u8 sum = 0;
+       u8 *end = buffer + length;
+
+       while (buffer < end)
+               sum = (u8) (sum + *(buffer++));
+       return sum;
+}
+
+/* All but ACPI_SIG_RSDP and ACPI_SIG_FACS: */
+#define MAX_ACPI_SIGNATURE 35
+static const char *table_sigs[MAX_ACPI_SIGNATURE] = {
+       ACPI_SIG_BERT, ACPI_SIG_CPEP, ACPI_SIG_ECDT, ACPI_SIG_EINJ,
+       ACPI_SIG_ERST, ACPI_SIG_HEST, ACPI_SIG_MADT, ACPI_SIG_MSCT,
+       ACPI_SIG_SBST, ACPI_SIG_SLIT, ACPI_SIG_SRAT, ACPI_SIG_ASF,
+       ACPI_SIG_BOOT, ACPI_SIG_DBGP, ACPI_SIG_DMAR, ACPI_SIG_HPET,
+       ACPI_SIG_IBFT, ACPI_SIG_IVRS, ACPI_SIG_MCFG, ACPI_SIG_MCHI,
+       ACPI_SIG_SLIC, ACPI_SIG_SPCR, ACPI_SIG_SPMI, ACPI_SIG_TCPA,
+       ACPI_SIG_UEFI, ACPI_SIG_WAET, ACPI_SIG_WDAT, ACPI_SIG_WDDT,
+       ACPI_SIG_WDRT, ACPI_SIG_DSDT, ACPI_SIG_FADT, ACPI_SIG_PSDT,
+       ACPI_SIG_RSDT, ACPI_SIG_XSDT, ACPI_SIG_SSDT };
+
+int __init acpi_initrd_table_override(void *start_addr, void *end_addr)
+{
+       int table_nr, sig;
+       unsigned long offset = 0, max_len = end_addr - start_addr;
+       char *p;
+
+       for (table_nr = 0; table_nr < ACPI_OVERRIDE_TABLES; table_nr++) {
+               struct acpi_table_header *table;
+               if (max_len < offset + sizeof(struct acpi_table_header)) {
+                       WARN_ON(1);
+                       return 0;
+               }
+               table = start_addr + offset;
+
+               for (sig = 0; sig < MAX_ACPI_SIGNATURE; sig++)
+                       if (!memcmp(table->signature, table_sigs[sig], 4))
+                               break;
+
+               if (sig >= MAX_ACPI_SIGNATURE)
+                       break;
+
+               if (max_len < offset + table->length) {
+                       WARN_ON(1);
+                       return 0;
+               }
+
+               if (acpi_table_checksum(start_addr + offset, table->length)) {
+                       WARN(1, "%4.4s has invalid checksum\n",
+                            table->signature);
+                       continue;
+               }
+               printk(KERN_INFO "%4.4s ACPI table found in initrd"
+                      " - size: %d\n", table->signature, table->length);
+
+               offset += table->length;
+               acpi_table_override_offset[table_nr] = offset;
+       }
+       if (!offset)
+               return 0;
+
+       acpi_tables_inram =
+               memblock_find_in_range(0, max_low_pfn_mapped << PAGE_SHIFT,
+                                      offset, PAGE_SIZE);
+       if (acpi_tables_inram == MEMBLOCK_ERROR)
+               panic("Cannot find place for ACPI override tables\n");
+
+       /*
+        * Only calling e820_add_reserve does not work and the
+        * tables are invalid (memory got used) later.
+        * memblock_x86_reserve_range works as expected and the tables
+        * won't get modified. But it's not enough because ioremap will
+        * complain later (used by acpi_os_map_memory) that the pages
+        * that should get mapped are not marked "reserved".
+        * Both memblock_x86_reserve_range and e820_add_region works fine.
+        */
+       memblock_x86_reserve_range(acpi_tables_inram,
+                                  acpi_tables_inram + offset,
+                                  "ACPI TABLE OVERRIDE");
+       e820_add_region(acpi_tables_inram, offset, E820_ACPI);
+       update_e820();
+
+       p = early_ioremap(acpi_tables_inram, offset);
+       memcpy(p, start_addr, offset);
+       early_iounmap(p, offset);
+       return offset;
+}
+
+#endif
+
 acpi_status
 acpi_os_table_override(struct acpi_table_header * existing_table,
                       struct acpi_table_header ** new_table)
@@ -522,6 +625,57 @@ acpi_os_table_override(struct acpi_table_header * existing_table,
        return AE_OK;
 }
 
+acpi_status
+acpi_os_phys_table_override(struct acpi_table_header *existing_table,
+                           acpi_physical_address *address, u32 *table_length)
+{
+
+#ifndef CONFIG_ACPI_INITRD_TABLE_OVERRIDE
+       *table_length = 0;
+       *address = 0;
+       return AE_OK;
+#else
+       int table_nr = 0;
+       *table_length = 0;
+       *address = 0;
+       for (; table_nr < ACPI_OVERRIDE_TABLES &&
+                    acpi_table_override_offset[table_nr]; table_nr++) {
+               int table_offset;
+               int table_len;
+               struct acpi_table_header *table;
+
+               if (table_nr == 0)
+                       table_offset = 0;
+               else
+                       table_offset = acpi_table_override_offset[table_nr - 1];
+
+               table_len = acpi_table_override_offset[table_nr] - table_offset;
+
+               table = acpi_os_map_memory(acpi_tables_inram + table_offset,
+                                          table_len);
+
+               if (memcmp(existing_table->signature, table->signature, 4)) {
+                       acpi_os_unmap_memory(table, table_len);
+                       continue;
+               }
+
+               /* Only override tables with matching oem id */
+               if (memcmp(table->oem_table_id, existing_table->oem_table_id,
+                          ACPI_OEM_TABLE_ID_SIZE)) {
+                       acpi_os_unmap_memory(table, table_len);
+                       continue;
+               }
+
+               acpi_os_unmap_memory(table, table_len);
+               *address = acpi_tables_inram + table_offset;
+               *table_length = table_len;
+               add_taint(TAINT_OVERRIDDEN_ACPI_TABLE);
+               break;
+       }
+       return AE_OK;
+#endif
+}
+
 static irqreturn_t acpi_irq(int irq, void *dev_id)
 {
        u32 handled;
index 4543b6f..0bef969 100644 (file)
@@ -95,6 +95,10 @@ acpi_status
 acpi_os_table_override(struct acpi_table_header *existing_table,
                       struct acpi_table_header **new_table);
 
+acpi_status
+acpi_os_phys_table_override(struct acpi_table_header *existing_table,
+                           acpi_physical_address *address, u32 *table_length);
+
 /*
  * Spinlock primitives
  */
index ba3c1b5..923ffa3 100644 (file)
@@ -76,6 +76,10 @@ typedef int (*acpi_table_handler) (struct acpi_table_header *table);
 
 typedef int (*acpi_table_entry_handler) (struct acpi_subtable_header *header, const unsigned long end);
 
+#ifdef CONFIG_ACPI_INITRD_TABLE_OVERRIDE
+int __init acpi_initrd_table_override(void *start_addr, void *end_addr);
+#endif
+
 char * __acpi_map_table (unsigned long phys_addr, unsigned long size);
 void __acpi_unmap_table(char *map, unsigned long size);
 int early_acpi_boot_init(void);
index 55289d2..c7694d9 100644 (file)
@@ -16,5 +16,8 @@ extern int initrd_below_start_ok;
 /* free_initrd_mem always gets called with the next two as arguments.. */
 extern unsigned long initrd_start, initrd_end;
 extern void free_initrd_mem(unsigned long, unsigned long);
+#ifdef CONFIG_ACPI_INITRD_TABLE_OVERRIDE
+extern unsigned long acpi_initrd_offset;
+#endif
 
 extern unsigned int real_root_dev;