Skip to content

Commit 18dd0bf

Browse files
committed
Merge branch 'x86-acpi-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip
Pull x86 ACPI update from Peter Anvin: "This is a patchset which didn't make the last merge window. It adds a debugging capability to feed ACPI tables via the initramfs. On a grander scope, it formalizes using the initramfs protocol for feeding arbitrary blobs which need to be accessed early to the kernel: they are fed first in the initramfs blob (lots of bootloaders can concatenate this at boot time, others can use a single file) in an uncompressed cpio archive using filenames starting with "kernel/". The ACPI maintainers requested that this patchset be fed via the x86 tree rather than the ACPI tree as the footprint in the general x86 code is much bigger than in the ACPI code proper." * 'x86-acpi-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip: X86 ACPI: Use #ifdef not #if for CONFIG_X86 check ACPI: Fix build when disabled ACPI: Document ACPI table overriding via initrd ACPI: Create acpi_table_taint() function to avoid code duplication ACPI: Implement physical address table override ACPI: Store valid ACPI tables passed via early initrd in reserved memblock areas x86, acpi: Introduce x86 arch specific arch_reserve_mem_area() for e820 handling lib: Add early cpio decoder
2 parents 2d9c8b5 + 385ddea commit 18dd0bf

File tree

9 files changed

+484
-12
lines changed

9 files changed

+484
-12
lines changed
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
Overriding ACPI tables via initrd
2+
=================================
3+
4+
1) Introduction (What is this about)
5+
2) What is this for
6+
3) How does it work
7+
4) References (Where to retrieve userspace tools)
8+
9+
1) What is this about
10+
---------------------
11+
12+
If the ACPI_INITRD_TABLE_OVERRIDE compile option is true, it is possible to
13+
override nearly any ACPI table provided by the BIOS with an instrumented,
14+
modified one.
15+
16+
For a full list of ACPI tables that can be overridden, take a look at
17+
the char *table_sigs[MAX_ACPI_SIGNATURE]; definition in drivers/acpi/osl.c
18+
All ACPI tables iasl (Intel's ACPI compiler and disassembler) knows should
19+
be overridable, except:
20+
- ACPI_SIG_RSDP (has a signature of 6 bytes)
21+
- ACPI_SIG_FACS (does not have an ordinary ACPI table header)
22+
Both could get implemented as well.
23+
24+
25+
2) What is this for
26+
-------------------
27+
28+
Please keep in mind that this is a debug option.
29+
ACPI tables should not get overridden for productive use.
30+
If BIOS ACPI tables are overridden the kernel will get tainted with the
31+
TAINT_OVERRIDDEN_ACPI_TABLE flag.
32+
Complain to your platform/BIOS vendor if you find a bug which is so sever
33+
that a workaround is not accepted in the Linux kernel.
34+
35+
Still, it can and should be enabled in any kernel, because:
36+
- There is no functional change with not instrumented initrds
37+
- It provides a powerful feature to easily debug and test ACPI BIOS table
38+
compatibility with the Linux kernel.
39+
40+
41+
3) How does it work
42+
-------------------
43+
44+
# Extract the machine's ACPI tables:
45+
cd /tmp
46+
acpidump >acpidump
47+
acpixtract -a acpidump
48+
# Disassemble, modify and recompile them:
49+
iasl -d *.dat
50+
# For example add this statement into a _PRT (PCI Routing Table) function
51+
# of the DSDT:
52+
Store("HELLO WORLD", debug)
53+
iasl -sa dsdt.dsl
54+
# Add the raw ACPI tables to an uncompressed cpio archive.
55+
# They must be put into a /kernel/firmware/acpi directory inside the
56+
# cpio archive.
57+
# The uncompressed cpio archive must be the first.
58+
# Other, typically compressed cpio archives, must be
59+
# concatenated on top of the uncompressed one.
60+
mkdir -p kernel/firmware/acpi
61+
cp dsdt.aml kernel/firmware/acpi
62+
# A maximum of: #define ACPI_OVERRIDE_TABLES 10
63+
# tables are currently allowed (see osl.c):
64+
iasl -sa facp.dsl
65+
iasl -sa ssdt1.dsl
66+
cp facp.aml kernel/firmware/acpi
67+
cp ssdt1.aml kernel/firmware/acpi
68+
# Create the uncompressed cpio archive and concatenate the original initrd
69+
# on top:
70+
find kernel | cpio -H newc --create > /boot/instrumented_initrd
71+
cat /boot/initrd >>/boot/instrumented_initrd
72+
# reboot with increased acpi debug level, e.g. boot params:
73+
acpi.debug_level=0x2 acpi.debug_layer=0xFFFFFFFF
74+
# and check your syslog:
75+
[ 1.268089] ACPI: PCI Interrupt Routing Table [\_SB_.PCI0._PRT]
76+
[ 1.272091] [ACPI Debug] String [0x0B] "HELLO WORLD"
77+
78+
iasl is able to disassemble and recompile quite a lot different,
79+
also static ACPI tables.
80+
81+
82+
4) Where to retrieve userspace tools
83+
------------------------------------
84+
85+
iasl and acpixtract are part of Intel's ACPICA project:
86+
http://acpica.org/
87+
and should be packaged by distributions (for example in the acpica package
88+
on SUSE).
89+
90+
acpidump can be found in Len Browns pmtools:
91+
ftp://kernel.org/pub/linux/kernel/people/lenb/acpi/utils/pmtools/acpidump
92+
This tool is also part of the acpica package on SUSE.
93+
Alternatively, used ACPI tables can be retrieved via sysfs in latest kernels:
94+
/sys/firmware/acpi/tables

arch/x86/kernel/acpi/boot.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1706,3 +1706,9 @@ int __acpi_release_global_lock(unsigned int *lock)
17061706
} while (unlikely (val != old));
17071707
return old & 0x1;
17081708
}
1709+
1710+
void __init arch_reserve_mem_area(acpi_physical_address addr, size_t size)
1711+
{
1712+
e820_add_region(addr, size, E820_ACPI);
1713+
update_e820();
1714+
}

arch/x86/kernel/setup.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -952,6 +952,10 @@ void __init setup_arch(char **cmdline_p)
952952

953953
reserve_initrd();
954954

955+
#if defined(CONFIG_ACPI) && defined(CONFIG_BLK_DEV_INITRD)
956+
acpi_initrd_override((void *)initrd_start, initrd_end - initrd_start);
957+
#endif
958+
955959
reserve_crashkernel();
956960

957961
vsmp_init();

drivers/acpi/Kconfig

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,15 @@ config ACPI_CUSTOM_DSDT
267267
bool
268268
default ACPI_CUSTOM_DSDT_FILE != ""
269269

270+
config ACPI_INITRD_TABLE_OVERRIDE
271+
bool "ACPI tables can be passed via uncompressed cpio in initrd"
272+
default n
273+
help
274+
This option provides functionality to override arbitrary ACPI tables
275+
via initrd. No functional change if no ACPI tables are passed via
276+
initrd, therefore it's safe to say Y.
277+
See Documentation/acpi/initrd_table_override.txt for details
278+
270279
config ACPI_BLACKLIST_YEAR
271280
int "Disable ACPI for systems before Jan 1st this year" if X86_32
272281
default 0

drivers/acpi/osl.c

Lines changed: 191 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -534,6 +534,137 @@ acpi_os_predefined_override(const struct acpi_predefined_names *init_val,
534534
return AE_OK;
535535
}
536536

537+
#ifdef CONFIG_ACPI_INITRD_TABLE_OVERRIDE
538+
#include <linux/earlycpio.h>
539+
#include <linux/memblock.h>
540+
541+
static u64 acpi_tables_addr;
542+
static int all_tables_size;
543+
544+
/* Copied from acpica/tbutils.c:acpi_tb_checksum() */
545+
u8 __init acpi_table_checksum(u8 *buffer, u32 length)
546+
{
547+
u8 sum = 0;
548+
u8 *end = buffer + length;
549+
550+
while (buffer < end)
551+
sum = (u8) (sum + *(buffer++));
552+
return sum;
553+
}
554+
555+
/* All but ACPI_SIG_RSDP and ACPI_SIG_FACS: */
556+
static const char * const table_sigs[] = {
557+
ACPI_SIG_BERT, ACPI_SIG_CPEP, ACPI_SIG_ECDT, ACPI_SIG_EINJ,
558+
ACPI_SIG_ERST, ACPI_SIG_HEST, ACPI_SIG_MADT, ACPI_SIG_MSCT,
559+
ACPI_SIG_SBST, ACPI_SIG_SLIT, ACPI_SIG_SRAT, ACPI_SIG_ASF,
560+
ACPI_SIG_BOOT, ACPI_SIG_DBGP, ACPI_SIG_DMAR, ACPI_SIG_HPET,
561+
ACPI_SIG_IBFT, ACPI_SIG_IVRS, ACPI_SIG_MCFG, ACPI_SIG_MCHI,
562+
ACPI_SIG_SLIC, ACPI_SIG_SPCR, ACPI_SIG_SPMI, ACPI_SIG_TCPA,
563+
ACPI_SIG_UEFI, ACPI_SIG_WAET, ACPI_SIG_WDAT, ACPI_SIG_WDDT,
564+
ACPI_SIG_WDRT, ACPI_SIG_DSDT, ACPI_SIG_FADT, ACPI_SIG_PSDT,
565+
ACPI_SIG_RSDT, ACPI_SIG_XSDT, ACPI_SIG_SSDT, NULL };
566+
567+
/* Non-fatal errors: Affected tables/files are ignored */
568+
#define INVALID_TABLE(x, path, name) \
569+
{ pr_err("ACPI OVERRIDE: " x " [%s%s]\n", path, name); continue; }
570+
571+
#define ACPI_HEADER_SIZE sizeof(struct acpi_table_header)
572+
573+
/* Must not increase 10 or needs code modification below */
574+
#define ACPI_OVERRIDE_TABLES 10
575+
576+
void __init acpi_initrd_override(void *data, size_t size)
577+
{
578+
int sig, no, table_nr = 0, total_offset = 0;
579+
long offset = 0;
580+
struct acpi_table_header *table;
581+
char cpio_path[32] = "kernel/firmware/acpi/";
582+
struct cpio_data file;
583+
struct cpio_data early_initrd_files[ACPI_OVERRIDE_TABLES];
584+
char *p;
585+
586+
if (data == NULL || size == 0)
587+
return;
588+
589+
for (no = 0; no < ACPI_OVERRIDE_TABLES; no++) {
590+
file = find_cpio_data(cpio_path, data, size, &offset);
591+
if (!file.data)
592+
break;
593+
594+
data += offset;
595+
size -= offset;
596+
597+
if (file.size < sizeof(struct acpi_table_header))
598+
INVALID_TABLE("Table smaller than ACPI header",
599+
cpio_path, file.name);
600+
601+
table = file.data;
602+
603+
for (sig = 0; table_sigs[sig]; sig++)
604+
if (!memcmp(table->signature, table_sigs[sig], 4))
605+
break;
606+
607+
if (!table_sigs[sig])
608+
INVALID_TABLE("Unknown signature",
609+
cpio_path, file.name);
610+
if (file.size != table->length)
611+
INVALID_TABLE("File length does not match table length",
612+
cpio_path, file.name);
613+
if (acpi_table_checksum(file.data, table->length))
614+
INVALID_TABLE("Bad table checksum",
615+
cpio_path, file.name);
616+
617+
pr_info("%4.4s ACPI table found in initrd [%s%s][0x%x]\n",
618+
table->signature, cpio_path, file.name, table->length);
619+
620+
all_tables_size += table->length;
621+
early_initrd_files[table_nr].data = file.data;
622+
early_initrd_files[table_nr].size = file.size;
623+
table_nr++;
624+
}
625+
if (table_nr == 0)
626+
return;
627+
628+
acpi_tables_addr =
629+
memblock_find_in_range(0, max_low_pfn_mapped << PAGE_SHIFT,
630+
all_tables_size, PAGE_SIZE);
631+
if (!acpi_tables_addr) {
632+
WARN_ON(1);
633+
return;
634+
}
635+
/*
636+
* Only calling e820_add_reserve does not work and the
637+
* tables are invalid (memory got used) later.
638+
* memblock_reserve works as expected and the tables won't get modified.
639+
* But it's not enough on X86 because ioremap will
640+
* complain later (used by acpi_os_map_memory) that the pages
641+
* that should get mapped are not marked "reserved".
642+
* Both memblock_reserve and e820_add_region (via arch_reserve_mem_area)
643+
* works fine.
644+
*/
645+
memblock_reserve(acpi_tables_addr, acpi_tables_addr + all_tables_size);
646+
arch_reserve_mem_area(acpi_tables_addr, all_tables_size);
647+
648+
p = early_ioremap(acpi_tables_addr, all_tables_size);
649+
650+
for (no = 0; no < table_nr; no++) {
651+
memcpy(p + total_offset, early_initrd_files[no].data,
652+
early_initrd_files[no].size);
653+
total_offset += early_initrd_files[no].size;
654+
}
655+
early_iounmap(p, all_tables_size);
656+
}
657+
#endif /* CONFIG_ACPI_INITRD_TABLE_OVERRIDE */
658+
659+
static void acpi_table_taint(struct acpi_table_header *table)
660+
{
661+
pr_warn(PREFIX
662+
"Override [%4.4s-%8.8s], this is unsafe: tainting kernel\n",
663+
table->signature, table->oem_table_id);
664+
add_taint(TAINT_OVERRIDDEN_ACPI_TABLE);
665+
}
666+
667+
537668
acpi_status
538669
acpi_os_table_override(struct acpi_table_header * existing_table,
539670
struct acpi_table_header ** new_table)
@@ -547,24 +678,73 @@ acpi_os_table_override(struct acpi_table_header * existing_table,
547678
if (strncmp(existing_table->signature, "DSDT", 4) == 0)
548679
*new_table = (struct acpi_table_header *)AmlCode;
549680
#endif
550-
if (*new_table != NULL) {
551-
printk(KERN_WARNING PREFIX "Override [%4.4s-%8.8s], "
552-
"this is unsafe: tainting kernel\n",
553-
existing_table->signature,
554-
existing_table->oem_table_id);
555-
add_taint(TAINT_OVERRIDDEN_ACPI_TABLE);
556-
}
681+
if (*new_table != NULL)
682+
acpi_table_taint(existing_table);
557683
return AE_OK;
558684
}
559685

560686
acpi_status
561687
acpi_os_physical_table_override(struct acpi_table_header *existing_table,
562-
acpi_physical_address * new_address,
563-
u32 *new_table_length)
688+
acpi_physical_address *address,
689+
u32 *table_length)
564690
{
565-
return AE_SUPPORT;
566-
}
691+
#ifndef CONFIG_ACPI_INITRD_TABLE_OVERRIDE
692+
*table_length = 0;
693+
*address = 0;
694+
return AE_OK;
695+
#else
696+
int table_offset = 0;
697+
struct acpi_table_header *table;
698+
699+
*table_length = 0;
700+
*address = 0;
701+
702+
if (!acpi_tables_addr)
703+
return AE_OK;
704+
705+
do {
706+
if (table_offset + ACPI_HEADER_SIZE > all_tables_size) {
707+
WARN_ON(1);
708+
return AE_OK;
709+
}
567710

711+
table = acpi_os_map_memory(acpi_tables_addr + table_offset,
712+
ACPI_HEADER_SIZE);
713+
714+
if (table_offset + table->length > all_tables_size) {
715+
acpi_os_unmap_memory(table, ACPI_HEADER_SIZE);
716+
WARN_ON(1);
717+
return AE_OK;
718+
}
719+
720+
table_offset += table->length;
721+
722+
if (memcmp(existing_table->signature, table->signature, 4)) {
723+
acpi_os_unmap_memory(table,
724+
ACPI_HEADER_SIZE);
725+
continue;
726+
}
727+
728+
/* Only override tables with matching oem id */
729+
if (memcmp(table->oem_table_id, existing_table->oem_table_id,
730+
ACPI_OEM_TABLE_ID_SIZE)) {
731+
acpi_os_unmap_memory(table,
732+
ACPI_HEADER_SIZE);
733+
continue;
734+
}
735+
736+
table_offset -= table->length;
737+
*table_length = table->length;
738+
acpi_os_unmap_memory(table, ACPI_HEADER_SIZE);
739+
*address = acpi_tables_addr + table_offset;
740+
break;
741+
} while (table_offset + ACPI_HEADER_SIZE < all_tables_size);
742+
743+
if (*address != 0)
744+
acpi_table_taint(existing_table);
745+
return AE_OK;
746+
#endif
747+
}
568748

569749
static irqreturn_t acpi_irq(int irq, void *dev_id)
570750
{

include/linux/acpi.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,14 @@ typedef int (*acpi_table_handler) (struct acpi_table_header *table);
7878

7979
typedef int (*acpi_table_entry_handler) (struct acpi_subtable_header *header, const unsigned long end);
8080

81+
#ifdef CONFIG_ACPI_INITRD_TABLE_OVERRIDE
82+
void acpi_initrd_override(void *data, size_t size);
83+
#else
84+
static inline void acpi_initrd_override(void *data, size_t size)
85+
{
86+
}
87+
#endif
88+
8189
char * __acpi_map_table (unsigned long phys_addr, unsigned long size);
8290
void __acpi_unmap_table(char *map, unsigned long size);
8391
int early_acpi_boot_init(void);
@@ -479,6 +487,14 @@ void acpi_os_set_prepare_sleep(int (*func)(u8 sleep_state,
479487

480488
acpi_status acpi_os_prepare_sleep(u8 sleep_state,
481489
u32 pm1a_control, u32 pm1b_control);
490+
#ifdef CONFIG_X86
491+
void arch_reserve_mem_area(acpi_physical_address addr, size_t size);
492+
#else
493+
static inline void arch_reserve_mem_area(acpi_physical_address addr,
494+
size_t size)
495+
{
496+
}
497+
#endif /* CONFIG_X86 */
482498
#else
483499
#define acpi_os_set_prepare_sleep(func, pm1a_ctrl, pm1b_ctrl) do { } while (0)
484500
#endif

0 commit comments

Comments
 (0)