402 lines
12 KiB
Plaintext
402 lines
12 KiB
Plaintext
From: Jeff Mahoney <jeffm@suse.com>
|
|
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 <eric.piel@tremplin-utc.net>.
|
|
|
|
13 Jan 2010 jeffm: Uses initramfs_{read,write} now to avoid
|
|
"scheduling while atomic" warnings.
|
|
|
|
Signed-off-by: Jeff Mahoney <jeffm@suse.com>
|
|
---
|
|
|
|
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 <table-name>.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 <linux/list.h>
|
|
#include <linux/jiffies.h>
|
|
#include <linux/semaphore.h>
|
|
+#include <linux/syscalls.h>
|
|
|
|
#include <asm/io.h>
|
|
#include <asm/uaccess.h>
|
|
@@ -51,6 +52,15 @@
|
|
#include <acpi/acpi_bus.h>
|
|
#include <acpi/processor.h>
|
|
|
|
+/* 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);
|
|
|