From: Jeff Mahoney Subject: ACPI: generic initramfs table override support References: bnc#533555 Patch-mainline: Probably never This patch allows the system administrator to override ACPI tables with versions provided in an initramfs. This works by moving the initialization of populate_rootfs earlier in the initialization so that we can use the VFS file system routines. The system is initialized enough to support this by acpi_early_init(). My understanding is that an early version of original patch posted at http://gaugusch.at/kernel.shtml may have done something similar. This version provides the infrastructure to override any ACPI table, but only provides support for overriding DSDT. If other tables are desired, extending the support is trivial. During early ACPI initialization, when the initramfs is still loaded, we go through a table of override entries which specify the name of the table to override, the file name that contains it, and a pointer to the data loaded from the file. The override tables and headers are kept in memory so that they available to the ACPI subsystem after the __init sections and the initramfs have been jettisoned. This patch is derived from the work by Éric Piel . 13 Jan 2010 jeffm: Uses initramfs_{read,write} now to avoid "scheduling while atomic" warnings. Signed-off-by: Jeff Mahoney --- Documentation/acpi/dsdt-override.txt | 8 + Documentation/acpi/initramfs-add-dsdt.sh | 43 +++++++ Documentation/acpi/table-override.txt | 21 +++ Documentation/kernel-parameters.txt | 4 drivers/acpi/Kconfig | 13 ++ drivers/acpi/bus.c | 7 + drivers/acpi/osl.c | 171 +++++++++++++++++++++++++++++++ include/acpi/acpiosxf.h | 3 8 files changed, 269 insertions(+), 1 deletion(-) --- a/Documentation/acpi/dsdt-override.txt +++ b/Documentation/acpi/dsdt-override.txt @@ -1,7 +1,13 @@ -Linux supports a method of overriding the BIOS DSDT: +Linux supports two methods of overriding the BIOS DSDT: CONFIG_ACPI_CUSTOM_DSDT builds the image into the kernel. +CONFIG_ACPI_CUSTOM_OVERRIDE_INITRAMFS loads the image from +the initramfs at boot-time. It is more flexible in that it +does not need to be built into the kernel and tables other +than DSDT can potentially be overridden. Please see +Documentation/acpi/table-override.txt for more information. + When to use this method is described in detail on the Linux/ACPI home page: http://www.lesswatts.org/projects/acpi/overridingDSDT.php --- /dev/null +++ b/Documentation/acpi/initramfs-add-dsdt.sh @@ -0,0 +1,43 @@ +#!/bin/bash +# Adds a DSDT file to the initrd (if it's an initramfs) +# first argument is the name of archive +# second argument is the name of the file to add +# The file will be copied as /DSDT.aml + +# 20060126: fix "Premature end of file" with some old cpio (Roland Robic) +# 20060205: this time it should really work + +# check the arguments +if [ $# -ne 2 ]; then + program_name=$(basename $0) + echo "\ +$program_name: too few arguments +Usage: $program_name initrd-name.img DSDT-to-add.aml +Adds a DSDT file to an initrd (in initramfs format) + + initrd-name.img: filename of the initrd in initramfs format + DSDT-to-add.aml: filename of the DSDT file to add + " 1>&2 + exit 1 +fi + +# we should check it's an initramfs + +tempcpio=$(mktemp -d) +# cleanup on exit, hangup, interrupt, quit, termination +trap 'rm -rf $tempcpio' 0 1 2 3 15 + +# extract the archive +gunzip -c "$1" > "$tempcpio"/initramfs.cpio || exit 1 + +# copy the DSDT file at the root of the directory so that we can call it "/DSDT.aml" +cp -f "$2" "$tempcpio"/DSDT.aml + +# add the file +cd "$tempcpio" +(echo DSDT.aml | cpio --quiet -H newc -o -A -O "$tempcpio"/initramfs.cpio) || exit 1 +cd "$OLDPWD" + +# re-compress the archive +gzip -c "$tempcpio"/initramfs.cpio > "$1" + --- /dev/null +++ b/Documentation/acpi/table-override.txt @@ -0,0 +1,21 @@ +CONFIG_ACPI_CUSTOM_OVERRIDE_INITRAMFS provides a mechanism for +the user to add table images to the initramfs for loading at +runtime. Tables used before expansion of the initramfs may not +be replaced. Fortunately this list is small and the one most +typically used, DSDT, is not one of them. + +In order to override a table, the image must be placed in the root +of the initramfs with a filename of .aml (e.g. DSDT.aml). + +As the ACPI subsystem initializes, it will load the tables into memory +and override them as the tables are needed. + +This option takes precedence over the in-kernel method provided by +the ACPI_CUSTOM_DSDT config option. + +When to use these methods is described in detail on the +Linux/ACPI home page: +http://www.lesswatts.org/projects/acpi/overridingDSDT.php + +Documentation/initramfs-add-dsdt.sh is provided for convenience +for use with the CONFIG_ACPI_CUSTOM_OVERRIDE_INITRAMFS method. --- a/Documentation/kernel-parameters.txt +++ b/Documentation/kernel-parameters.txt @@ -217,6 +217,10 @@ and is between 256 and 4096 characters. acpi_no_auto_ssdt [HW,ACPI] Disable automatic loading of SSDT + acpi_no_initrd_override [KNL,ACPI] + acpi_no_initramfs_override [KNL,ACPI] + Disable loading custom ACPI tables from the initramfs + acpi_os_name= [HW,ACPI] Tell ACPI BIOS the name of the OS Format: To spoof as Windows 98: ="Microsoft Windows" --- a/drivers/acpi/Kconfig +++ b/drivers/acpi/Kconfig @@ -260,6 +260,19 @@ config ACPI_CUSTOM_DSDT bool default ACPI_CUSTOM_DSDT_FILE != "" +config ACPI_CUSTOM_OVERRIDE_INITRAMFS + bool "Load ACPI override tables from initramfs" + depends on BLK_DEV_INITRD + default n + help + This option supports loading custom replacement tables by optionally + loading them from the initramfs. + + See Documentation/acpi/table-override.txt + + If you are not using this feature now, but may use it later, + it is safe to say Y here. + config ACPI_BLACKLIST_YEAR int "Disable ACPI for systems before Jan 1st this year" if X86_32 default 0 --- a/drivers/acpi/bus.c +++ b/drivers/acpi/bus.c @@ -665,6 +665,13 @@ void __init acpi_early_init(void) goto error0; } + status = acpi_load_override_tables(); + if (ACPI_FAILURE(status)) { + printk(KERN_ERR PREFIX + "Unable to load Override Tables\n"); + goto error0; + } + status = acpi_load_tables(); if (ACPI_FAILURE(status)) { printk(KERN_ERR PREFIX --- a/drivers/acpi/osl.c +++ b/drivers/acpi/osl.c @@ -43,6 +43,7 @@ #include #include #include +#include #include #include @@ -51,6 +52,15 @@ #include #include +/* We need these to manipulate the global table array. The existing + * accessors in acpica/ only pass back the table header and we need + * the descriptor. */ +#include "acpica/acconfig.h" +#include "acpica/aclocal.h" +#include "acpica/acglobal.h" +#include "acpica/acutils.h" +#include "acpica/actables.h" + #define _COMPONENT ACPI_OS_SERVICES ACPI_MODULE_NAME("osl"); #define PREFIX "ACPI: " @@ -98,6 +108,23 @@ static DEFINE_SPINLOCK(acpi_res_lock); #define OSI_STRING_LENGTH_MAX 64 /* arbitrary */ static char osi_additional_string[OSI_STRING_LENGTH_MAX]; +#ifdef CONFIG_ACPI_CUSTOM_OVERRIDE_INITRAMFS +static int acpi_no_initrd_override; +static int __init acpi_no_initrd_override_setup(char *s) +{ + acpi_no_initrd_override = 1; + return 1; +} + +static int __init acpi_no_initramfs_override_setup(char *s) +{ + return acpi_no_initrd_override_setup(s); +} + +__setup("acpi_no_initrd_override", acpi_no_initrd_override_setup); +__setup("acpi_no_initramfs_override", acpi_no_initramfs_override_setup); +#endif + /* * The story of _OSI(Linux) * @@ -352,6 +379,146 @@ acpi_os_predefined_override(const struct return AE_OK; } +#ifdef CONFIG_ACPI_CUSTOM_OVERRIDE_INITRAMFS +struct acpi_override_table_entry +{ + const char *name; + struct acpi_table_header *table; +}; + +static struct acpi_override_table_entry acpi_override_table_entries[] = { + { .name = "DSDT", }, + {} +}; + + +ssize_t initramfs_read(unsigned int fd, const char * buf, size_t count); +acpi_status __init +acpi_load_one_override_table(struct acpi_override_table_entry *entry) +{ + int fd, ret; + acpi_status err = AE_OK; + char filename[10]; /* /DSDT.aml\0 */ + struct kstat stat; + + snprintf(filename, sizeof(filename), "/%.4s.aml", entry->name); + + fd = sys_open(filename, O_RDONLY, 0); + if (fd < 0) + return AE_NOT_FOUND; + + ret = vfs_fstat(fd, &stat); + if (ret < 0) { + printk(KERN_ERR "ACPI: fstat failed while trying to read %s\n", + filename); + err = AE_ERROR; + goto out; + } + + entry->table = kmalloc(stat.size, GFP_KERNEL); + if (!entry->table) { + printk(KERN_ERR "ACPI: Could not allocate memory to " + "override %s\n", entry->name); + err = AE_NO_MEMORY; + goto out; + } + + ret = initramfs_read(fd, (char *)entry->table, stat.size); + sys_close(fd); + if (ret != stat.size) { + printk(KERN_ERR "ACPI: Failed to read %s from initramfs\n", + entry->name); + err = AE_ERROR; + goto out; + } + +out: + if (err != AE_OK) { + kfree(entry->table); + entry->table = NULL; + } + sys_close(fd); + return ret; +} + +static void __init +acpi_replace_table(struct acpi_table_desc *table, struct acpi_table_header *new) +{ + /* This is the top part of acpi_load_table */ + memset(table, 0, sizeof(*table)); + table->address = ACPI_PTR_TO_PHYSADDR(new); + table->pointer = new; + table->length = new->length; + table->flags |= ACPI_TABLE_ORIGIN_OVERRIDE; + table->flags |= ACPI_TABLE_ORIGIN_ALLOCATED; + memcpy(table->signature.ascii, new->signature, ACPI_NAME_SIZE); +} + +/* This replaces tables already opportunistically loaded, but not used. + * If the acpica code provided a table descriptor lookup then we wouldn't + * need to open code this. */ +static void __init +acpi_override_tables(void) +{ + struct acpi_table_header *new = NULL; + struct acpi_table_desc *table; + acpi_status status; + int i; + + /* This is early enough that we don't need the mutex yet */ + for (i = 0; i < acpi_gbl_root_table_list.count; ++i) { + if (acpi_tb_is_table_loaded(i)) + continue; + + table = &acpi_gbl_root_table_list.tables[i]; + if (!table->pointer) + status = acpi_tb_verify_table(table); + + if (ACPI_FAILURE(status) || !table->pointer) + continue; + + status = acpi_os_table_override(table->pointer, &new); + if (ACPI_SUCCESS(status) && new) { + acpi_replace_table(table, new); + acpi_tb_print_table_header(table->address, new); + } + } +} + +acpi_status __init +acpi_load_override_tables(void) +{ + struct acpi_override_table_entry *entry = acpi_override_table_entries; + while (entry && entry->name) { + acpi_load_one_override_table(entry); + entry++; + } + + acpi_override_tables(); + return AE_OK; +} + +static struct acpi_table_header * +acpi_get_override_table(const char *name) +{ + struct acpi_override_table_entry *entry = acpi_override_table_entries; + + while (entry && entry->name) { + if (!memcmp(name, entry->name, ACPI_NAME_SIZE)) + return entry->table;; + entry++; + } + + return NULL; +} +#else +acpi_status +acpi_load_override_tables(void) +{ + return AE_OK; +} +#endif + acpi_status acpi_os_table_override(struct acpi_table_header * existing_table, struct acpi_table_header ** new_table) @@ -365,6 +532,10 @@ acpi_os_table_override(struct acpi_table if (strncmp(existing_table->signature, "DSDT", 4) == 0) *new_table = (struct acpi_table_header *)AmlCode; #endif +#ifdef CONFIG_ACPI_CUSTOM_OVERRIDE_INITRAMFS + if (!acpi_no_initrd_override) + *new_table = acpi_get_override_table(existing_table->signature); +#endif if (*new_table != NULL) { printk(KERN_WARNING PREFIX "Override [%4.4s-%8.8s], " "this is unsafe: tainting kernel\n", --- a/include/acpi/acpiosxf.h +++ b/include/acpi/acpiosxf.h @@ -92,6 +92,9 @@ acpi_os_predefined_override(const struct acpi_string * new_val); acpi_status +acpi_load_override_tables(void); + +acpi_status acpi_os_table_override(struct acpi_table_header *existing_table, struct acpi_table_header **new_table);