[PATCH 2/2] ACPI: Override arbitrary ACPI tables via initrd for debugging

From: Thomas Renninger
Date: Wed Jul 18 2012 - 06:36:48 EST


Details can be found in:
Documentation/acpi/initrd_table_override.txt

Signed-off-by: Thomas Renninger <trenn@xxxxxxx>
CC: eric.piel@xxxxxxxxxxxxxxxx
CC: vojcek@xxxxxxx
CC: Lin Ming <ming.m.lin@xxxxxxxxx>
CC: lenb@xxxxxxxxxx
CC: robert.moore@xxxxxxxxx
CC: hpa@xxxxxxxxx
---
Documentation/acpi/initrd_table_override.txt | 119 ++++++++++++++++
drivers/acpi/Kconfig | 10 ++
drivers/acpi/osl.c | 193 ++++++++++++++++++++++++--
include/linux/acpi.h | 6 +
include/linux/initrd.h | 4 +-
init/initramfs.c | 23 +++-
init/initrd_early.c | 10 ++
7 files changed, 349 insertions(+), 16 deletions(-)
create mode 100644 Documentation/acpi/initrd_table_override.txt

diff --git a/Documentation/acpi/initrd_table_override.txt b/Documentation/acpi/initrd_table_override.txt
new file mode 100644
index 0000000..e985dea
--- /dev/null
+++ b/Documentation/acpi/initrd_table_override.txt
@@ -0,0 +1,119 @@
+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.
+
+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
+# Add the raw ACPI tables to an uncompressed cpio archive.
+# They must be put into /kernel/firmware/acpi directory inside the cpio
+# archive.
+# If you want to override other firmware files early (for example CPU
+# microcode), you must use only one uncompressed cpio archive and it must
+# be the first. Other, typically compressed cpio archives, must be
+# concatenated on top of the uncompressed one.
+# For further info read the "Accessing initrd data early" chapter in
+# Documtenation/initrd.txt.
+mkdir -p /tmp/early_cpio/kernel/firmware/acpi
+cp TBL1.dat /tmp/early_cpio/kernel/firmware/acpi
+cat TBL2.dat /tmp/early_cpio/kernel/firmware/acpi
+cat TBL3.dat /tmp/early_cpio/kernel/firmware/acpi
+cd /tmp/early_cpio
+find . | cpio -H newc --create > /boot/instrumented_initrd
+cat /boot/initrd >>/boot/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
diff --git a/drivers/acpi/Kconfig b/drivers/acpi/Kconfig
index 8099895..9d49efb 100644
--- a/drivers/acpi/Kconfig
+++ b/drivers/acpi/Kconfig
@@ -261,6 +261,16 @@ config ACPI_CUSTOM_DSDT
bool
default ACPI_CUSTOM_DSDT_FILE != ""

+config ACPI_INITRD_TABLE_OVERRIDE
+ bool
+ depends on EARLY_INITRD
+ default y
+ help
+ This option provides functionality to override arbitrary ACPI tables
+ via initrd. No functional change if no ACPI tables are passed via
+ 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
diff --git a/drivers/acpi/osl.c b/drivers/acpi/osl.c
index c3881b2..059be34 100644
--- a/drivers/acpi/osl.c
+++ b/drivers/acpi/osl.c
@@ -45,6 +45,7 @@
#include <linux/list.h>
#include <linux/jiffies.h>
#include <linux/semaphore.h>
+#include <linux/memblock.h>

#include <asm/io.h>
#include <asm/uaccess.h>
@@ -534,6 +535,126 @@ acpi_os_predefined_override(const struct acpi_predefined_names *init_val,
return AE_OK;
}

+#ifdef CONFIG_ACPI_INITRD_TABLE_OVERRIDE
+#include <asm/e820.h>
+
+#define ACPI_OVERRIDE_TABLES 10
+
+__initdata static struct{
+ void *data;
+ int size;
+} early_initrd_files[ACPI_OVERRIDE_TABLES];
+static __initdata int table_nr;
+static int all_tables_size;
+static u64 acpi_tables_addr;
+
+/* 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: */
+static const char *table_sigs[] = {
+ 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, NULL };
+
+/* Non-fatal errors: Affected tables/files are ignored */
+#define INVALID_TABLE(x, name) \
+ { printk(KERN_ERR "ACPI OVERRIDE: " x " [%s]\n", name); return 1; }
+
+#define ACPI_HEADER_SIZE sizeof(struct acpi_table_header)
+
+int __init acpi_initrd_table_override(void *data, int size, const char *name)
+{
+ int sig;
+ struct acpi_table_header *table;
+
+ if (table_nr >= ACPI_OVERRIDE_TABLES)
+ INVALID_TABLE("Too much early tables - ignoring", name);
+
+ if (size < sizeof(struct acpi_table_header))
+ INVALID_TABLE("Table smaller than ACPI header", name);
+
+ table = data;
+
+ for (sig = 0; table_sigs[sig]; sig++)
+ if (!memcmp(table->signature, table_sigs[sig], 4))
+ break;
+
+ if (!table_sigs[sig])
+ INVALID_TABLE("Unknown signature", name);
+
+ if (size != table->length)
+ INVALID_TABLE("File length does not match table length", name);
+
+ if (acpi_table_checksum(data, table->length))
+ INVALID_TABLE("Bad table checksum", name);
+
+ printk(KERN_INFO "%4.4s ACPI table found in initrd [%s][%d]\n",
+ table->signature, name, table->length);
+
+ all_tables_size += table->length;
+ early_initrd_files[table_nr].data = data;
+ early_initrd_files[table_nr].size = size;
+ table_nr++;
+ return 0;
+}
+
+void __init acpi_initrd_finalize(void)
+{
+ int i, offset = 0;
+ char *p;
+
+ acpi_tables_addr =
+ memblock_find_in_range(0, max_low_pfn_mapped << PAGE_SHIFT,
+ all_tables_size, PAGE_SIZE);
+ if (!acpi_tables_addr)
+ 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_reserve(acpi_tables_addr, acpi_tables_addr + all_tables_size);
+ e820_add_region(acpi_tables_addr, all_tables_size, E820_ACPI);
+ update_e820();
+ p = early_ioremap(acpi_tables_addr, all_tables_size);
+
+ for (i = 0; i < table_nr; i++) {
+ memcpy(p + offset, early_initrd_files[i].data,
+ early_initrd_files[i].size);
+ offset += early_initrd_files[i].size;
+ }
+ early_iounmap(p, all_tables_size);
+}
+#endif /* CONFIG_ACPI_INITRD_TABLE_OVERRIDE */
+
+static void acpi_table_taint(struct acpi_table_header *table)
+{
+ printk(KERN_WARNING PREFIX "Override [%4.4s-%8.8s], "
+ "this is unsafe: tainting kernel\n",
+ table->signature, table->oem_table_id);
+ add_taint(TAINT_OVERRIDDEN_ACPI_TABLE);
+}
+
acpi_status
acpi_os_table_override(struct acpi_table_header * existing_table,
struct acpi_table_header ** new_table)
@@ -547,24 +668,74 @@ acpi_os_table_override(struct acpi_table_header * existing_table,
if (strncmp(existing_table->signature, "DSDT", 4) == 0)
*new_table = (struct acpi_table_header *)AmlCode;
#endif
- if (*new_table != NULL) {
- printk(KERN_WARNING PREFIX "Override [%4.4s-%8.8s], "
- "this is unsafe: tainting kernel\n",
- existing_table->signature,
- existing_table->oem_table_id);
- add_taint(TAINT_OVERRIDDEN_ACPI_TABLE);
- }
+ if (*new_table != NULL)
+ acpi_table_taint(existing_table);
return AE_OK;
}

acpi_status
acpi_os_physical_table_override(struct acpi_table_header *existing_table,
- acpi_physical_address * new_address,
- u32 *new_table_length)
+ acpi_physical_address *address,
+ u32 *table_length)
{
- return AE_SUPPORT;
-}
+#ifndef CONFIG_ACPI_INITRD_TABLE_OVERRIDE
+ *table_length = 0;
+ *address = 0;
+ return AE_OK;
+#else
+ int table_offset = 0;
+ struct acpi_table_header *table;
+
+ *table_length = 0;
+ *address = 0;
+
+ if (!acpi_tables_addr)
+ return AE_OK;
+
+ do {
+ if (table_offset + ACPI_HEADER_SIZE > all_tables_size) {
+ WARN_ON(1);
+ return AE_OK;
+ }
+
+ table = acpi_os_map_memory(acpi_tables_addr + table_offset,
+ ACPI_HEADER_SIZE);

+ if (table_offset + table->length > all_tables_size) {
+ acpi_os_unmap_memory(table, ACPI_HEADER_SIZE);
+ WARN_ON(1);
+ return AE_OK;
+ }
+
+ table_offset += table->length;
+
+ if (memcmp(existing_table->signature, table->signature, 4)) {
+ acpi_os_unmap_memory(table,
+ ACPI_HEADER_SIZE);
+ 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,
+ ACPI_HEADER_SIZE);
+ continue;
+ }
+
+ table_offset -= table->length;
+ *table_length = table->length;
+ acpi_os_unmap_memory(table, ACPI_HEADER_SIZE);
+ *address = acpi_tables_addr + table_offset;
+ add_taint(TAINT_OVERRIDDEN_ACPI_TABLE);
+ break;
+ } while (table_offset + ACPI_HEADER_SIZE < all_tables_size);
+
+ if (*address != 0)
+ acpi_table_taint(existing_table);
+ return AE_OK;
+#endif
+}

static irqreturn_t acpi_irq(int irq, void *dev_id)
{
diff --git a/include/linux/acpi.h b/include/linux/acpi.h
index f421dd8..9fb292c 100644
--- a/include/linux/acpi.h
+++ b/include/linux/acpi.h
@@ -76,6 +76,12 @@ 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 *data,
+ int size, const char *name);
+void __init acpi_initrd_finalize(void);
+#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);
diff --git a/include/linux/initrd.h b/include/linux/initrd.h
index 3fe262e..8b26e4d 100644
--- a/include/linux/initrd.h
+++ b/include/linux/initrd.h
@@ -23,9 +23,9 @@ extern unsigned int real_root_dev;
#define MAX_EARLY_INITRD_CB 16

#ifdef CONFIG_EARLY_INITRD
-extern int early_initrd_find_cpio_data(const char *data, size_t len);
+extern void early_initrd_find_cpio_data(const char *data, size_t len);
#else
-static int early_initrd_find_cpio_data(const char *data, size_t len)
+static void early_initrd_find_cpio_data(const char *data, size_t len)
{
return 0;
}
diff --git a/init/initramfs.c b/init/initramfs.c
index 84c6bf1..70a1972 100644
--- a/init/initramfs.c
+++ b/init/initramfs.c
@@ -411,8 +411,10 @@ static int __init flush_buffer(void *bufv, unsigned len)
buf += written;
len -= written;
state = Reset;
- } else
+ } else {
+ pr_info("junk in compressed archive 3 %u", len);
error("junk in compressed archive");
+ }
}
return origLen;
}
@@ -427,6 +429,10 @@ static char * __init unpack_to_rootfs(char *buf, unsigned len)
decompress_fn decompress;
const char *compress_name;
static __initdata char msg_buf[64];
+ int skipped = 0;
+ unsigned long tot_written = 0;
+
+ pr_info("%s: 0x%p len: %u\n", __FUNCTION__, buf, len);

header_buf = kmalloc(110, GFP_KERNEL);
symlink_buf = kmalloc(PATH_MAX + N_ALIGN(PATH_MAX) + 1, GFP_KERNEL);
@@ -441,13 +447,17 @@ static char * __init unpack_to_rootfs(char *buf, unsigned len)
while (!message && len) {
loff_t saved_offset = this_header;
if (*buf == '0' && !(this_header & 3)) {
+ pr_info("Starting...\n");
state = Start;
written = write_buffer(buf, len);
buf += written;
len -= written;
+ pr_info("... %u written, remaining: %u\n",
+ written, len);
continue;
}
if (!*buf) {
+ skipped++;
buf++;
len--;
this_header++;
@@ -456,21 +466,28 @@ static char * __init unpack_to_rootfs(char *buf, unsigned len)
this_header = 0;
decompress = decompress_method(buf, len, &compress_name);
if (decompress) {
+ pr_info("Decompress: %u - buf[0]: 0x%x - buf[1]: 0x%x", len, *buf, *(buf + 1));
+ pr_info("Decompressing via %s\n", compress_name);
res = decompress(buf, len, NULL, flush_buffer, NULL,
&my_inptr, error);
if (res)
error("decompressor failed");
} else if (compress_name) {
+ pr_info("Not decompressing via %s\n", compress_name);
if (!message) {
snprintf(msg_buf, sizeof msg_buf,
"compression method %s not configured",
compress_name);
message = msg_buf;
}
- } else
+ } else {
+ pr_info("junk in compressed archive 1 %u - buf[0]: 0x%x - buf[1]: 0x%x", len, *buf, *(buf + 1));
error("junk in compressed archive");
- if (state != Reset)
+ }
+ if (state != Reset) {
+ pr_info("junk in compressed archive 2 %u", len);
error("junk in compressed archive");
+ }
this_header = saved_offset + my_inptr;
buf += my_inptr;
len -= my_inptr;
diff --git a/init/initrd_early.c b/init/initrd_early.c
index c657a4b..35d8480 100644
--- a/init/initrd_early.c
+++ b/init/initrd_early.c
@@ -1,6 +1,9 @@
#include <linux/kernel.h>
#include <linux/string.h>
#include <linux/initrd.h>
+#ifdef CONFIG_ACPI_INITRD_TABLE_OVERRIDE
+#include <linux/acpi.h>
+#endif

struct initrd_early_data {
/* Path where relevant files can be found in uncompressed cpio */
@@ -18,6 +21,13 @@ struct initrd_early_data {
*/
static __initdata struct initrd_early_data initrd_early_callbacks[] =
{
+#ifdef CONFIG_ACPI_INITRD_TABLE_OVERRIDE
+ {
+ .namesp = "kernel/firmware/acpi/",
+ .cb = acpi_initrd_table_override,
+ .final = acpi_initrd_finalize,
+ },
+#endif
{
.namesp = NULL,
}
--
1.7.6.1

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/