diff options
Diffstat (limited to 'arch/i386/kernel/machine_kexec.c')
-rw-r--r-- | arch/i386/kernel/machine_kexec.c | 140 |
1 files changed, 50 insertions, 90 deletions
diff --git a/arch/i386/kernel/machine_kexec.c b/arch/i386/kernel/machine_kexec.c index 6b1ae6ba76f0..91966bafb3dc 100644 --- a/arch/i386/kernel/machine_kexec.c +++ b/arch/i386/kernel/machine_kexec.c | |||
@@ -9,6 +9,7 @@ | |||
9 | #include <linux/mm.h> | 9 | #include <linux/mm.h> |
10 | #include <linux/kexec.h> | 10 | #include <linux/kexec.h> |
11 | #include <linux/delay.h> | 11 | #include <linux/delay.h> |
12 | #include <linux/init.h> | ||
12 | #include <asm/pgtable.h> | 13 | #include <asm/pgtable.h> |
13 | #include <asm/pgalloc.h> | 14 | #include <asm/pgalloc.h> |
14 | #include <asm/tlbflush.h> | 15 | #include <asm/tlbflush.h> |
@@ -20,70 +21,13 @@ | |||
20 | #include <asm/system.h> | 21 | #include <asm/system.h> |
21 | 22 | ||
22 | #define PAGE_ALIGNED __attribute__ ((__aligned__(PAGE_SIZE))) | 23 | #define PAGE_ALIGNED __attribute__ ((__aligned__(PAGE_SIZE))) |
23 | 24 | static u32 kexec_pgd[1024] PAGE_ALIGNED; | |
24 | #define L0_ATTR (_PAGE_PRESENT | _PAGE_RW | _PAGE_ACCESSED | _PAGE_DIRTY) | 25 | #ifdef CONFIG_X86_PAE |
25 | #define L1_ATTR (_PAGE_PRESENT | _PAGE_RW | _PAGE_ACCESSED | _PAGE_DIRTY) | 26 | static u32 kexec_pmd0[1024] PAGE_ALIGNED; |
26 | #define L2_ATTR (_PAGE_PRESENT) | 27 | static u32 kexec_pmd1[1024] PAGE_ALIGNED; |
27 | |||
28 | #define LEVEL0_SIZE (1UL << 12UL) | ||
29 | |||
30 | #ifndef CONFIG_X86_PAE | ||
31 | #define LEVEL1_SIZE (1UL << 22UL) | ||
32 | static u32 pgtable_level1[1024] PAGE_ALIGNED; | ||
33 | |||
34 | static void identity_map_page(unsigned long address) | ||
35 | { | ||
36 | unsigned long level1_index, level2_index; | ||
37 | u32 *pgtable_level2; | ||
38 | |||
39 | /* Find the current page table */ | ||
40 | pgtable_level2 = __va(read_cr3()); | ||
41 | |||
42 | /* Find the indexes of the physical address to identity map */ | ||
43 | level1_index = (address % LEVEL1_SIZE)/LEVEL0_SIZE; | ||
44 | level2_index = address / LEVEL1_SIZE; | ||
45 | |||
46 | /* Identity map the page table entry */ | ||
47 | pgtable_level1[level1_index] = address | L0_ATTR; | ||
48 | pgtable_level2[level2_index] = __pa(pgtable_level1) | L1_ATTR; | ||
49 | |||
50 | /* Flush the tlb so the new mapping takes effect. | ||
51 | * Global tlb entries are not flushed but that is not an issue. | ||
52 | */ | ||
53 | load_cr3(pgtable_level2); | ||
54 | } | ||
55 | |||
56 | #else | ||
57 | #define LEVEL1_SIZE (1UL << 21UL) | ||
58 | #define LEVEL2_SIZE (1UL << 30UL) | ||
59 | static u64 pgtable_level1[512] PAGE_ALIGNED; | ||
60 | static u64 pgtable_level2[512] PAGE_ALIGNED; | ||
61 | |||
62 | static void identity_map_page(unsigned long address) | ||
63 | { | ||
64 | unsigned long level1_index, level2_index, level3_index; | ||
65 | u64 *pgtable_level3; | ||
66 | |||
67 | /* Find the current page table */ | ||
68 | pgtable_level3 = __va(read_cr3()); | ||
69 | |||
70 | /* Find the indexes of the physical address to identity map */ | ||
71 | level1_index = (address % LEVEL1_SIZE)/LEVEL0_SIZE; | ||
72 | level2_index = (address % LEVEL2_SIZE)/LEVEL1_SIZE; | ||
73 | level3_index = address / LEVEL2_SIZE; | ||
74 | |||
75 | /* Identity map the page table entry */ | ||
76 | pgtable_level1[level1_index] = address | L0_ATTR; | ||
77 | pgtable_level2[level2_index] = __pa(pgtable_level1) | L1_ATTR; | ||
78 | set_64bit(&pgtable_level3[level3_index], | ||
79 | __pa(pgtable_level2) | L2_ATTR); | ||
80 | |||
81 | /* Flush the tlb so the new mapping takes effect. | ||
82 | * Global tlb entries are not flushed but that is not an issue. | ||
83 | */ | ||
84 | load_cr3(pgtable_level3); | ||
85 | } | ||
86 | #endif | 28 | #endif |
29 | static u32 kexec_pte0[1024] PAGE_ALIGNED; | ||
30 | static u32 kexec_pte1[1024] PAGE_ALIGNED; | ||
87 | 31 | ||
88 | static void set_idt(void *newidt, __u16 limit) | 32 | static void set_idt(void *newidt, __u16 limit) |
89 | { | 33 | { |
@@ -127,16 +71,6 @@ static void load_segments(void) | |||
127 | #undef __STR | 71 | #undef __STR |
128 | } | 72 | } |
129 | 73 | ||
130 | typedef asmlinkage NORET_TYPE void (*relocate_new_kernel_t)( | ||
131 | unsigned long indirection_page, | ||
132 | unsigned long reboot_code_buffer, | ||
133 | unsigned long start_address, | ||
134 | unsigned int has_pae) ATTRIB_NORET; | ||
135 | |||
136 | extern const unsigned char relocate_new_kernel[]; | ||
137 | extern void relocate_new_kernel_end(void); | ||
138 | extern const unsigned int relocate_new_kernel_size; | ||
139 | |||
140 | /* | 74 | /* |
141 | * A architecture hook called to validate the | 75 | * A architecture hook called to validate the |
142 | * proposed image and prepare the control pages | 76 | * proposed image and prepare the control pages |
@@ -169,25 +103,29 @@ void machine_kexec_cleanup(struct kimage *image) | |||
169 | */ | 103 | */ |
170 | NORET_TYPE void machine_kexec(struct kimage *image) | 104 | NORET_TYPE void machine_kexec(struct kimage *image) |
171 | { | 105 | { |
172 | unsigned long page_list; | 106 | unsigned long page_list[PAGES_NR]; |
173 | unsigned long reboot_code_buffer; | 107 | void *control_page; |
174 | |||
175 | relocate_new_kernel_t rnk; | ||
176 | 108 | ||
177 | /* Interrupts aren't acceptable while we reboot */ | 109 | /* Interrupts aren't acceptable while we reboot */ |
178 | local_irq_disable(); | 110 | local_irq_disable(); |
179 | 111 | ||
180 | /* Compute some offsets */ | 112 | control_page = page_address(image->control_code_page); |
181 | reboot_code_buffer = page_to_pfn(image->control_code_page) | 113 | memcpy(control_page, relocate_kernel, PAGE_SIZE); |
182 | << PAGE_SHIFT; | 114 | |
183 | page_list = image->head; | 115 | page_list[PA_CONTROL_PAGE] = __pa(control_page); |
184 | 116 | page_list[VA_CONTROL_PAGE] = (unsigned long)relocate_kernel; | |
185 | /* Set up an identity mapping for the reboot_code_buffer */ | 117 | page_list[PA_PGD] = __pa(kexec_pgd); |
186 | identity_map_page(reboot_code_buffer); | 118 | page_list[VA_PGD] = (unsigned long)kexec_pgd; |
187 | 119 | #ifdef CONFIG_X86_PAE | |
188 | /* copy it out */ | 120 | page_list[PA_PMD_0] = __pa(kexec_pmd0); |
189 | memcpy((void *)reboot_code_buffer, relocate_new_kernel, | 121 | page_list[VA_PMD_0] = (unsigned long)kexec_pmd0; |
190 | relocate_new_kernel_size); | 122 | page_list[PA_PMD_1] = __pa(kexec_pmd1); |
123 | page_list[VA_PMD_1] = (unsigned long)kexec_pmd1; | ||
124 | #endif | ||
125 | page_list[PA_PTE_0] = __pa(kexec_pte0); | ||
126 | page_list[VA_PTE_0] = (unsigned long)kexec_pte0; | ||
127 | page_list[PA_PTE_1] = __pa(kexec_pte1); | ||
128 | page_list[VA_PTE_1] = (unsigned long)kexec_pte1; | ||
191 | 129 | ||
192 | /* The segment registers are funny things, they have both a | 130 | /* The segment registers are funny things, they have both a |
193 | * visible and an invisible part. Whenever the visible part is | 131 | * visible and an invisible part. Whenever the visible part is |
@@ -206,6 +144,28 @@ NORET_TYPE void machine_kexec(struct kimage *image) | |||
206 | set_idt(phys_to_virt(0),0); | 144 | set_idt(phys_to_virt(0),0); |
207 | 145 | ||
208 | /* now call it */ | 146 | /* now call it */ |
209 | rnk = (relocate_new_kernel_t) reboot_code_buffer; | 147 | relocate_kernel((unsigned long)image->head, (unsigned long)page_list, |
210 | (*rnk)(page_list, reboot_code_buffer, image->start, cpu_has_pae); | 148 | image->start, cpu_has_pae); |
149 | } | ||
150 | |||
151 | /* crashkernel=size@addr specifies the location to reserve for | ||
152 | * a crash kernel. By reserving this memory we guarantee | ||
153 | * that linux never sets it up as a DMA target. | ||
154 | * Useful for holding code to do something appropriate | ||
155 | * after a kernel panic. | ||
156 | */ | ||
157 | static int __init parse_crashkernel(char *arg) | ||
158 | { | ||
159 | unsigned long size, base; | ||
160 | size = memparse(arg, &arg); | ||
161 | if (*arg == '@') { | ||
162 | base = memparse(arg+1, &arg); | ||
163 | /* FIXME: Do I want a sanity check | ||
164 | * to validate the memory range? | ||
165 | */ | ||
166 | crashk_res.start = base; | ||
167 | crashk_res.end = base + size - 1; | ||
168 | } | ||
169 | return 0; | ||
211 | } | 170 | } |
171 | early_param("crashkernel", parse_crashkernel); | ||