diff options
-rw-r--r-- | Documentation/acpi/initrd_table_override.txt | 94 | ||||
-rw-r--r-- | arch/x86/kernel/acpi/boot.c | 6 | ||||
-rw-r--r-- | arch/x86/kernel/setup.c | 4 | ||||
-rw-r--r-- | drivers/acpi/Kconfig | 9 | ||||
-rw-r--r-- | drivers/acpi/osl.c | 202 | ||||
-rw-r--r-- | include/linux/acpi.h | 16 | ||||
-rw-r--r-- | include/linux/earlycpio.h | 17 | ||||
-rw-r--r-- | lib/Makefile | 3 | ||||
-rw-r--r-- | lib/earlycpio.c | 145 |
9 files changed, 484 insertions, 12 deletions
diff --git a/Documentation/acpi/initrd_table_override.txt b/Documentation/acpi/initrd_table_override.txt new file mode 100644 index 000000000000..35c3f5415476 --- /dev/null +++ b/Documentation/acpi/initrd_table_override.txt | |||
@@ -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 | ||
diff --git a/arch/x86/kernel/acpi/boot.c b/arch/x86/kernel/acpi/boot.c index e48cafcf92ae..bacf4b0d91f4 100644 --- a/arch/x86/kernel/acpi/boot.c +++ b/arch/x86/kernel/acpi/boot.c | |||
@@ -1706,3 +1706,9 @@ int __acpi_release_global_lock(unsigned int *lock) | |||
1706 | } while (unlikely (val != old)); | 1706 | } while (unlikely (val != old)); |
1707 | return old & 0x1; | 1707 | return old & 0x1; |
1708 | } | 1708 | } |
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 | } | ||
diff --git a/arch/x86/kernel/setup.c b/arch/x86/kernel/setup.c index c228322ca180..23ddd558fbd5 100644 --- a/arch/x86/kernel/setup.c +++ b/arch/x86/kernel/setup.c | |||
@@ -952,6 +952,10 @@ void __init setup_arch(char **cmdline_p) | |||
952 | 952 | ||
953 | reserve_initrd(); | 953 | reserve_initrd(); |
954 | 954 | ||
955 | #if defined(CONFIG_ACPI) && defined(CONFIG_BLK_DEV_INITRD) | ||
956 | acpi_initrd_override((void *)initrd_start, initrd_end - initrd_start); | ||
957 | #endif | ||
958 | |||
955 | reserve_crashkernel(); | 959 | reserve_crashkernel(); |
956 | 960 | ||
957 | vsmp_init(); | 961 | vsmp_init(); |
diff --git a/drivers/acpi/Kconfig b/drivers/acpi/Kconfig index 0300bf612946..38c5078da11d 100644 --- a/drivers/acpi/Kconfig +++ b/drivers/acpi/Kconfig | |||
@@ -267,6 +267,15 @@ config ACPI_CUSTOM_DSDT | |||
267 | bool | 267 | bool |
268 | default ACPI_CUSTOM_DSDT_FILE != "" | 268 | default ACPI_CUSTOM_DSDT_FILE != "" |
269 | 269 | ||
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 | |||
270 | config ACPI_BLACKLIST_YEAR | 279 | config ACPI_BLACKLIST_YEAR |
271 | int "Disable ACPI for systems before Jan 1st this year" if X86_32 | 280 | int "Disable ACPI for systems before Jan 1st this year" if X86_32 |
272 | default 0 | 281 | default 0 |
diff --git a/drivers/acpi/osl.c b/drivers/acpi/osl.c index 6dc4a2b1e956..3ff267861541 100644 --- a/drivers/acpi/osl.c +++ b/drivers/acpi/osl.c | |||
@@ -534,6 +534,137 @@ acpi_os_predefined_override(const struct acpi_predefined_names *init_val, | |||
534 | return AE_OK; | 534 | return AE_OK; |
535 | } | 535 | } |
536 | 536 | ||
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 | |||
537 | acpi_status | 668 | acpi_status |
538 | acpi_os_table_override(struct acpi_table_header * existing_table, | 669 | acpi_os_table_override(struct acpi_table_header * existing_table, |
539 | struct acpi_table_header ** new_table) | 670 | struct acpi_table_header ** new_table) |
@@ -547,24 +678,73 @@ acpi_os_table_override(struct acpi_table_header * existing_table, | |||
547 | if (strncmp(existing_table->signature, "DSDT", 4) == 0) | 678 | if (strncmp(existing_table->signature, "DSDT", 4) == 0) |
548 | *new_table = (struct acpi_table_header *)AmlCode; | 679 | *new_table = (struct acpi_table_header *)AmlCode; |
549 | #endif | 680 | #endif |
550 | if (*new_table != NULL) { | 681 | if (*new_table != NULL) |
551 | printk(KERN_WARNING PREFIX "Override [%4.4s-%8.8s], " | 682 | acpi_table_taint(existing_table); |
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 | } | ||
557 | return AE_OK; | 683 | return AE_OK; |
558 | } | 684 | } |
559 | 685 | ||
560 | acpi_status | 686 | acpi_status |
561 | acpi_os_physical_table_override(struct acpi_table_header *existing_table, | 687 | acpi_os_physical_table_override(struct acpi_table_header *existing_table, |
562 | acpi_physical_address * new_address, | 688 | acpi_physical_address *address, |
563 | u32 *new_table_length) | 689 | u32 *table_length) |
564 | { | 690 | { |
565 | return AE_SUPPORT; | 691 | #ifndef CONFIG_ACPI_INITRD_TABLE_OVERRIDE |
566 | } | 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 | } | ||
567 | 710 | ||
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 | } | ||
568 | 748 | ||
569 | static irqreturn_t acpi_irq(int irq, void *dev_id) | 749 | static irqreturn_t acpi_irq(int irq, void *dev_id) |
570 | { | 750 | { |
diff --git a/include/linux/acpi.h b/include/linux/acpi.h index c33fa3ce9b7c..3994d7790b23 100644 --- a/include/linux/acpi.h +++ b/include/linux/acpi.h | |||
@@ -78,6 +78,14 @@ typedef int (*acpi_table_handler) (struct acpi_table_header *table); | |||
78 | 78 | ||
79 | typedef int (*acpi_table_entry_handler) (struct acpi_subtable_header *header, const unsigned long end); | 79 | typedef int (*acpi_table_entry_handler) (struct acpi_subtable_header *header, const unsigned long end); |
80 | 80 | ||
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 | |||
81 | char * __acpi_map_table (unsigned long phys_addr, unsigned long size); | 89 | char * __acpi_map_table (unsigned long phys_addr, unsigned long size); |
82 | void __acpi_unmap_table(char *map, unsigned long size); | 90 | void __acpi_unmap_table(char *map, unsigned long size); |
83 | int early_acpi_boot_init(void); | 91 | int early_acpi_boot_init(void); |
@@ -479,6 +487,14 @@ void acpi_os_set_prepare_sleep(int (*func)(u8 sleep_state, | |||
479 | 487 | ||
480 | acpi_status acpi_os_prepare_sleep(u8 sleep_state, | 488 | acpi_status acpi_os_prepare_sleep(u8 sleep_state, |
481 | u32 pm1a_control, u32 pm1b_control); | 489 | 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 */ | ||
482 | #else | 498 | #else |
483 | #define acpi_os_set_prepare_sleep(func, pm1a_ctrl, pm1b_ctrl) do { } while (0) | 499 | #define acpi_os_set_prepare_sleep(func, pm1a_ctrl, pm1b_ctrl) do { } while (0) |
484 | #endif | 500 | #endif |
diff --git a/include/linux/earlycpio.h b/include/linux/earlycpio.h new file mode 100644 index 000000000000..111f46d83d00 --- /dev/null +++ b/include/linux/earlycpio.h | |||
@@ -0,0 +1,17 @@ | |||
1 | #ifndef _LINUX_EARLYCPIO_H | ||
2 | #define _LINUX_EARLYCPIO_H | ||
3 | |||
4 | #include <linux/types.h> | ||
5 | |||
6 | #define MAX_CPIO_FILE_NAME 18 | ||
7 | |||
8 | struct cpio_data { | ||
9 | void *data; | ||
10 | size_t size; | ||
11 | char name[MAX_CPIO_FILE_NAME]; | ||
12 | }; | ||
13 | |||
14 | struct cpio_data find_cpio_data(const char *path, void *data, size_t len, | ||
15 | long *offset); | ||
16 | |||
17 | #endif /* _LINUX_EARLYCPIO_H */ | ||
diff --git a/lib/Makefile b/lib/Makefile index e3723c7527da..e2152fa7ff4d 100644 --- a/lib/Makefile +++ b/lib/Makefile | |||
@@ -12,7 +12,8 @@ lib-y := ctype.o string.o vsprintf.o cmdline.o \ | |||
12 | idr.o int_sqrt.o extable.o \ | 12 | idr.o int_sqrt.o extable.o \ |
13 | sha1.o md5.o irq_regs.o reciprocal_div.o argv_split.o \ | 13 | sha1.o md5.o irq_regs.o reciprocal_div.o argv_split.o \ |
14 | proportions.o flex_proportions.o prio_heap.o ratelimit.o show_mem.o \ | 14 | proportions.o flex_proportions.o prio_heap.o ratelimit.o show_mem.o \ |
15 | is_single_threaded.o plist.o decompress.o kobject_uevent.o | 15 | is_single_threaded.o plist.o decompress.o kobject_uevent.o \ |
16 | earlycpio.o | ||
16 | 17 | ||
17 | lib-$(CONFIG_MMU) += ioremap.o | 18 | lib-$(CONFIG_MMU) += ioremap.o |
18 | lib-$(CONFIG_SMP) += cpumask.o | 19 | lib-$(CONFIG_SMP) += cpumask.o |
diff --git a/lib/earlycpio.c b/lib/earlycpio.c new file mode 100644 index 000000000000..8078ef49cb79 --- /dev/null +++ b/lib/earlycpio.c | |||
@@ -0,0 +1,145 @@ | |||
1 | /* ----------------------------------------------------------------------- * | ||
2 | * | ||
3 | * Copyright 2012 Intel Corporation; author H. Peter Anvin | ||
4 | * | ||
5 | * This file is part of the Linux kernel, and is made available | ||
6 | * under the terms of the GNU General Public License version 2, as | ||
7 | * published by the Free Software Foundation. | ||
8 | * | ||
9 | * This program is distributed in the hope it will be useful, but | ||
10 | * WITHOUT ANY WARRANTY; without even the implied warranty of | ||
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
12 | * General Public License for more details. | ||
13 | * | ||
14 | * ----------------------------------------------------------------------- */ | ||
15 | |||
16 | /* | ||
17 | * earlycpio.c | ||
18 | * | ||
19 | * Find a specific cpio member; must precede any compressed content. | ||
20 | * This is used to locate data items in the initramfs used by the | ||
21 | * kernel itself during early boot (before the main initramfs is | ||
22 | * decompressed.) It is the responsibility of the initramfs creator | ||
23 | * to ensure that these items are uncompressed at the head of the | ||
24 | * blob. Depending on the boot loader or package tool that may be a | ||
25 | * separate file or part of the same file. | ||
26 | */ | ||
27 | |||
28 | #include <linux/earlycpio.h> | ||
29 | #include <linux/kernel.h> | ||
30 | #include <linux/string.h> | ||
31 | |||
32 | enum cpio_fields { | ||
33 | C_MAGIC, | ||
34 | C_INO, | ||
35 | C_MODE, | ||
36 | C_UID, | ||
37 | C_GID, | ||
38 | C_NLINK, | ||
39 | C_MTIME, | ||
40 | C_FILESIZE, | ||
41 | C_MAJ, | ||
42 | C_MIN, | ||
43 | C_RMAJ, | ||
44 | C_RMIN, | ||
45 | C_NAMESIZE, | ||
46 | C_CHKSUM, | ||
47 | C_NFIELDS | ||
48 | }; | ||
49 | |||
50 | /** | ||
51 | * cpio_data find_cpio_data - Search for files in an uncompressed cpio | ||
52 | * @path: The directory to search for, including a slash at the end | ||
53 | * @data: Pointer to the the cpio archive or a header inside | ||
54 | * @len: Remaining length of the cpio based on data pointer | ||
55 | * @offset: When a matching file is found, this is the offset to the | ||
56 | * beginning of the cpio. It can be used to iterate through | ||
57 | * the cpio to find all files inside of a directory path | ||
58 | * | ||
59 | * @return: struct cpio_data containing the address, length and | ||
60 | * filename (with the directory path cut off) of the found file. | ||
61 | * If you search for a filename and not for files in a directory, | ||
62 | * pass the absolute path of the filename in the cpio and make sure | ||
63 | * the match returned an empty filename string. | ||
64 | */ | ||
65 | |||
66 | struct cpio_data __cpuinit find_cpio_data(const char *path, void *data, | ||
67 | size_t len, long *offset) | ||
68 | { | ||
69 | const size_t cpio_header_len = 8*C_NFIELDS - 2; | ||
70 | struct cpio_data cd = { NULL, 0, "" }; | ||
71 | const char *p, *dptr, *nptr; | ||
72 | unsigned int ch[C_NFIELDS], *chp, v; | ||
73 | unsigned char c, x; | ||
74 | size_t mypathsize = strlen(path); | ||
75 | int i, j; | ||
76 | |||
77 | p = data; | ||
78 | |||
79 | while (len > cpio_header_len) { | ||
80 | if (!*p) { | ||
81 | /* All cpio headers need to be 4-byte aligned */ | ||
82 | p += 4; | ||
83 | len -= 4; | ||
84 | continue; | ||
85 | } | ||
86 | |||
87 | j = 6; /* The magic field is only 6 characters */ | ||
88 | chp = ch; | ||
89 | for (i = C_NFIELDS; i; i--) { | ||
90 | v = 0; | ||
91 | while (j--) { | ||
92 | v <<= 4; | ||
93 | c = *p++; | ||
94 | |||
95 | x = c - '0'; | ||
96 | if (x < 10) { | ||
97 | v += x; | ||
98 | continue; | ||
99 | } | ||
100 | |||
101 | x = (c | 0x20) - 'a'; | ||
102 | if (x < 6) { | ||
103 | v += x + 10; | ||
104 | continue; | ||
105 | } | ||
106 | |||
107 | goto quit; /* Invalid hexadecimal */ | ||
108 | } | ||
109 | *chp++ = v; | ||
110 | j = 8; /* All other fields are 8 characters */ | ||
111 | } | ||
112 | |||
113 | if ((ch[C_MAGIC] - 0x070701) > 1) | ||
114 | goto quit; /* Invalid magic */ | ||
115 | |||
116 | len -= cpio_header_len; | ||
117 | |||
118 | dptr = PTR_ALIGN(p + ch[C_NAMESIZE], 4); | ||
119 | nptr = PTR_ALIGN(dptr + ch[C_FILESIZE], 4); | ||
120 | |||
121 | if (nptr > p + len || dptr < p || nptr < dptr) | ||
122 | goto quit; /* Buffer overrun */ | ||
123 | |||
124 | if ((ch[C_MODE] & 0170000) == 0100000 && | ||
125 | ch[C_NAMESIZE] >= mypathsize && | ||
126 | !memcmp(p, path, mypathsize)) { | ||
127 | *offset = (long)nptr - (long)data; | ||
128 | if (ch[C_NAMESIZE] - mypathsize >= MAX_CPIO_FILE_NAME) { | ||
129 | pr_warn( | ||
130 | "File %s exceeding MAX_CPIO_FILE_NAME [%d]\n", | ||
131 | p, MAX_CPIO_FILE_NAME); | ||
132 | } | ||
133 | strlcpy(cd.name, p + mypathsize, MAX_CPIO_FILE_NAME); | ||
134 | |||
135 | cd.data = (void *)dptr; | ||
136 | cd.size = ch[C_FILESIZE]; | ||
137 | return cd; /* Found it! */ | ||
138 | } | ||
139 | len -= (nptr - p); | ||
140 | p = nptr; | ||
141 | } | ||
142 | |||
143 | quit: | ||
144 | return cd; | ||
145 | } | ||