diff options
author | David Gibson <david@gibson.dropbear.id.au> | 2007-03-04 22:24:52 -0500 |
---|---|---|
committer | Paul Mackerras <paulus@samba.org> | 2007-03-12 22:35:01 -0400 |
commit | 79c8541924a220964f9f2cbed31eaa9fdb042eab (patch) | |
tree | 94e75bf65ea5cb0d40dfae7215ae432e1f914296 /arch | |
parent | ad9d2716cfc1cda5a7e0d7bc0db45e3af8a4adbb (diff) |
[POWERPC] zImage: Cleanup and improve prep_kernel()
This patch rewrites prep_kernel() in the zImage wrapper code to be
clearer and more flexible. Notable changes:
- Handling of the initrd image from prep_kernel() has moved
into a new prep_initrd() function.
- The address of the initrd image is now added as device tree
properties, as the kernel expects.
- We only copy a packaged initrd image to a new location if it
is in danger of being clobbered when the kernel moves to its final
location, instead of always.
- By default we decompress the kernel directly to address 0,
instead of requiring it to relocate itself. Platforms (such as OF)
where doing this could clobber still-live firmware data structures can
override the vmlinux_alloc hook to provide an alternate place to
decompress the kernel.
- We no longer pass lots of information between functions in
global variables.
Signed-off-by: David Gibson <dwg@au1.ibm.com>
Signed-off-by: Paul Mackerras <paulus@samba.org>
Diffstat (limited to 'arch')
-rw-r--r-- | arch/powerpc/boot/main.c | 167 | ||||
-rw-r--r-- | arch/powerpc/boot/of.c | 12 | ||||
-rw-r--r-- | arch/powerpc/boot/ops.h | 1 |
3 files changed, 114 insertions, 66 deletions
diff --git a/arch/powerpc/boot/main.c b/arch/powerpc/boot/main.c index 404620a9e733..05de6cfafeeb 100644 --- a/arch/powerpc/boot/main.c +++ b/arch/powerpc/boot/main.c | |||
@@ -33,24 +33,21 @@ extern char _dtb_end[]; | |||
33 | static struct gunzip_state gzstate; | 33 | static struct gunzip_state gzstate; |
34 | 34 | ||
35 | struct addr_range { | 35 | struct addr_range { |
36 | unsigned long addr; | 36 | void *addr; |
37 | unsigned long size; | 37 | unsigned long size; |
38 | unsigned long memsize; | ||
39 | }; | 38 | }; |
40 | static struct addr_range vmlinux; | ||
41 | static struct addr_range vmlinuz; | ||
42 | static struct addr_range initrd; | ||
43 | |||
44 | static unsigned long elfoffset; | ||
45 | static int is_64bit; | ||
46 | 39 | ||
47 | static char elfheader[256]; | 40 | struct elf_info { |
41 | unsigned long loadsize; | ||
42 | unsigned long memsize; | ||
43 | unsigned long elfoffset; | ||
44 | }; | ||
48 | 45 | ||
49 | typedef void (*kernel_entry_t)(unsigned long, unsigned long, void *); | 46 | typedef void (*kernel_entry_t)(unsigned long, unsigned long, void *); |
50 | 47 | ||
51 | #undef DEBUG | 48 | #undef DEBUG |
52 | 49 | ||
53 | static int is_elf64(void *hdr) | 50 | static int parse_elf64(void *hdr, struct elf_info *info) |
54 | { | 51 | { |
55 | Elf64_Ehdr *elf64 = hdr; | 52 | Elf64_Ehdr *elf64 = hdr; |
56 | Elf64_Phdr *elf64ph; | 53 | Elf64_Phdr *elf64ph; |
@@ -74,15 +71,14 @@ static int is_elf64(void *hdr) | |||
74 | if (i >= (unsigned int)elf64->e_phnum) | 71 | if (i >= (unsigned int)elf64->e_phnum) |
75 | return 0; | 72 | return 0; |
76 | 73 | ||
77 | elfoffset = (unsigned long)elf64ph->p_offset; | 74 | info->loadsize = (unsigned long)elf64ph->p_filesz; |
78 | vmlinux.size = (unsigned long)elf64ph->p_filesz; | 75 | info->memsize = (unsigned long)elf64ph->p_memsz; |
79 | vmlinux.memsize = (unsigned long)elf64ph->p_memsz; | 76 | info->elfoffset = (unsigned long)elf64ph->p_offset; |
80 | 77 | ||
81 | is_64bit = 1; | ||
82 | return 1; | 78 | return 1; |
83 | } | 79 | } |
84 | 80 | ||
85 | static int is_elf32(void *hdr) | 81 | static int parse_elf32(void *hdr, struct elf_info *info) |
86 | { | 82 | { |
87 | Elf32_Ehdr *elf32 = hdr; | 83 | Elf32_Ehdr *elf32 = hdr; |
88 | Elf32_Phdr *elf32ph; | 84 | Elf32_Phdr *elf32ph; |
@@ -98,7 +94,6 @@ static int is_elf32(void *hdr) | |||
98 | elf32->e_machine == EM_PPC)) | 94 | elf32->e_machine == EM_PPC)) |
99 | return 0; | 95 | return 0; |
100 | 96 | ||
101 | elf32 = (Elf32_Ehdr *)elfheader; | ||
102 | elf32ph = (Elf32_Phdr *) ((unsigned long)elf32 + elf32->e_phoff); | 97 | elf32ph = (Elf32_Phdr *) ((unsigned long)elf32 + elf32->e_phoff); |
103 | for (i = 0; i < elf32->e_phnum; i++, elf32ph++) | 98 | for (i = 0; i < elf32->e_phnum; i++, elf32ph++) |
104 | if (elf32ph->p_type == PT_LOAD) | 99 | if (elf32ph->p_type == PT_LOAD) |
@@ -106,24 +101,26 @@ static int is_elf32(void *hdr) | |||
106 | if (i >= elf32->e_phnum) | 101 | if (i >= elf32->e_phnum) |
107 | return 0; | 102 | return 0; |
108 | 103 | ||
109 | elfoffset = elf32ph->p_offset; | 104 | info->loadsize = elf32ph->p_filesz; |
110 | vmlinux.size = elf32ph->p_filesz; | 105 | info->memsize = elf32ph->p_memsz; |
111 | vmlinux.memsize = elf32ph->p_memsz; | 106 | info->elfoffset = elf32ph->p_offset; |
112 | return 1; | 107 | return 1; |
113 | } | 108 | } |
114 | 109 | ||
115 | static void prep_kernel(unsigned long a1, unsigned long a2) | 110 | static struct addr_range prep_kernel(void) |
116 | { | 111 | { |
112 | char elfheader[256]; | ||
113 | void *vmlinuz_addr = _vmlinux_start; | ||
114 | unsigned long vmlinuz_size = _vmlinux_end - _vmlinux_start; | ||
115 | void *addr = 0; | ||
116 | struct elf_info ei; | ||
117 | int len; | 117 | int len; |
118 | 118 | ||
119 | vmlinuz.addr = (unsigned long)_vmlinux_start; | ||
120 | vmlinuz.size = (unsigned long)(_vmlinux_end - _vmlinux_start); | ||
121 | |||
122 | /* gunzip the ELF header of the kernel */ | 119 | /* gunzip the ELF header of the kernel */ |
123 | gunzip_start(&gzstate, (void *)vmlinuz.addr, vmlinuz.size); | 120 | gunzip_start(&gzstate, vmlinuz_addr, vmlinuz_size); |
124 | gunzip_exactly(&gzstate, elfheader, sizeof(elfheader)); | 121 | gunzip_exactly(&gzstate, elfheader, sizeof(elfheader)); |
125 | 122 | ||
126 | if (!is_elf64(elfheader) && !is_elf32(elfheader)) { | 123 | if (!parse_elf64(elfheader, &ei) && !parse_elf32(elfheader, &ei)) { |
127 | printf("Error: not a valid PPC32 or PPC64 ELF file!\n\r"); | 124 | printf("Error: not a valid PPC32 or PPC64 ELF file!\n\r"); |
128 | exit(); | 125 | exit(); |
129 | } | 126 | } |
@@ -135,55 +132,92 @@ static void prep_kernel(unsigned long a1, unsigned long a2) | |||
135 | * the kernel bss must be claimed (it will be zero'd by the | 132 | * the kernel bss must be claimed (it will be zero'd by the |
136 | * kernel itself) | 133 | * kernel itself) |
137 | */ | 134 | */ |
138 | printf("Allocating 0x%lx bytes for kernel ...\n\r", vmlinux.memsize); | 135 | printf("Allocating 0x%lx bytes for kernel ...\n\r", ei.memsize); |
139 | vmlinux.addr = (unsigned long)malloc(vmlinux.memsize); | 136 | |
140 | if (vmlinux.addr == 0) { | 137 | if (platform_ops.vmlinux_alloc) { |
141 | printf("Can't allocate memory for kernel image !\n\r"); | 138 | addr = platform_ops.vmlinux_alloc(ei.memsize); |
142 | exit(); | 139 | } else { |
140 | if ((unsigned long)_start < ei.memsize) { | ||
141 | printf("Insufficient memory for kernel at address 0!" | ||
142 | " (_start=%lx)\n\r", _start); | ||
143 | exit(); | ||
144 | } | ||
143 | } | 145 | } |
144 | 146 | ||
147 | /* Finally, gunzip the kernel */ | ||
148 | printf("gunzipping (0x%p <- 0x%p:0x%p)...", addr, | ||
149 | vmlinuz_addr, vmlinuz_addr+vmlinuz_size); | ||
150 | /* discard up to the actual load data */ | ||
151 | gunzip_discard(&gzstate, ei.elfoffset - sizeof(elfheader)); | ||
152 | len = gunzip_finish(&gzstate, addr, ei.memsize); | ||
153 | printf("done 0x%lx bytes\n\r", len); | ||
154 | |||
155 | flush_cache(addr, ei.loadsize); | ||
156 | |||
157 | return (struct addr_range){addr, ei.memsize}; | ||
158 | } | ||
159 | |||
160 | static struct addr_range prep_initrd(struct addr_range vmlinux, | ||
161 | unsigned long initrd_addr, | ||
162 | unsigned long initrd_size) | ||
163 | { | ||
164 | void *devp; | ||
165 | u32 initrd_start, initrd_end; | ||
166 | |||
167 | /* If we have an image attached to us, it overrides anything | ||
168 | * supplied by the loader. */ | ||
169 | if (_initrd_end > _initrd_start) { | ||
170 | printf("Attached initrd image at 0x%p-0x%p\n\r", | ||
171 | _initrd_start, _initrd_end); | ||
172 | initrd_addr = (unsigned long)_initrd_start; | ||
173 | initrd_size = _initrd_end - _initrd_start; | ||
174 | } else if (initrd_size > 0) { | ||
175 | printf("Using loader supplied ramdisk at 0x%lx-0x%lx\n\r", | ||
176 | initrd_addr, initrd_addr + initrd_size); | ||
177 | } | ||
178 | |||
179 | /* If there's no initrd at all, we're done */ | ||
180 | if (! initrd_size) | ||
181 | return (struct addr_range){0, 0}; | ||
182 | |||
145 | /* | 183 | /* |
146 | * Now find the initrd | 184 | * If the initrd is too low it will be clobbered when the |
147 | * | 185 | * kernel relocates to its final location. In this case, |
148 | * First see if we have an image attached to us. If so | 186 | * allocate a safer place and move it. |
149 | * allocate memory for it and copy it there. | ||
150 | */ | 187 | */ |
151 | initrd.size = (unsigned long)(_initrd_end - _initrd_start); | 188 | if (initrd_addr < vmlinux.size) { |
152 | initrd.memsize = initrd.size; | 189 | void *old_addr = (void *)initrd_addr; |
153 | if (initrd.size > 0) { | 190 | |
154 | printf("Allocating 0x%lx bytes for initrd ...\n\r", | 191 | printf("Allocating 0x%lx bytes for initrd ...\n\r", |
155 | initrd.size); | 192 | initrd_size); |
156 | initrd.addr = (unsigned long)malloc((u32)initrd.size); | 193 | initrd_addr = (unsigned long)malloc(initrd_size); |
157 | if (initrd.addr == 0) { | 194 | if (! initrd_addr) { |
158 | printf("Can't allocate memory for initial " | 195 | printf("Can't allocate memory for initial " |
159 | "ramdisk !\n\r"); | 196 | "ramdisk !\n\r"); |
160 | exit(); | 197 | exit(); |
161 | } | 198 | } |
162 | printf("initial ramdisk moving 0x%lx <- 0x%lx " | 199 | printf("Relocating initrd 0x%p <- 0x%p (0x%lx bytes)\n\r", |
163 | "(0x%lx bytes)\n\r", initrd.addr, | 200 | initrd_addr, old_addr, initrd_size); |
164 | (unsigned long)_initrd_start, initrd.size); | 201 | memmove((void *)initrd_addr, old_addr, initrd_size); |
165 | memmove((void *)initrd.addr, (void *)_initrd_start, | ||
166 | initrd.size); | ||
167 | printf("initrd head: 0x%lx\n\r", | ||
168 | *((unsigned long *)initrd.addr)); | ||
169 | } else if (a2 != 0) { | ||
170 | /* Otherwise, see if yaboot or another loader gave us an initrd */ | ||
171 | initrd.addr = a1; | ||
172 | initrd.memsize = initrd.size = a2; | ||
173 | printf("Using loader supplied initrd at 0x%lx (0x%lx bytes)\n\r", | ||
174 | initrd.addr, initrd.size); | ||
175 | } | 202 | } |
176 | 203 | ||
177 | /* Eventually gunzip the kernel */ | 204 | printf("initrd head: 0x%lx\n\r", *((unsigned long *)initrd_addr)); |
178 | printf("gunzipping (0x%lx <- 0x%lx:0x%0lx)...", | 205 | |
179 | vmlinux.addr, vmlinuz.addr, vmlinuz.addr+vmlinuz.size); | 206 | /* Tell the kernel initrd address via device tree */ |
180 | /* discard up to the actual load data */ | 207 | devp = finddevice("/chosen"); |
181 | gunzip_discard(&gzstate, elfoffset - sizeof(elfheader)); | 208 | if (! devp) { |
182 | len = gunzip_finish(&gzstate, (void *)vmlinux.addr, | 209 | printf("Device tree has no chosen node!\n\r"); |
183 | vmlinux.memsize); | 210 | exit(); |
184 | printf("done 0x%lx bytes\n\r", len); | 211 | } |
212 | |||
213 | initrd_start = (u32)initrd_addr; | ||
214 | initrd_end = (u32)initrd_addr + initrd_size; | ||
215 | |||
216 | setprop(devp, "linux,initrd-start", &initrd_start, | ||
217 | sizeof(initrd_start)); | ||
218 | setprop(devp, "linux,initrd-end", &initrd_end, sizeof(initrd_end)); | ||
185 | 219 | ||
186 | flush_cache((void *)vmlinux.addr, vmlinux.size); | 220 | return (struct addr_range){(void *)initrd_addr, initrd_size}; |
187 | } | 221 | } |
188 | 222 | ||
189 | /* A buffer that may be edited by tools operating on a zImage binary so as to | 223 | /* A buffer that may be edited by tools operating on a zImage binary so as to |
@@ -223,6 +257,7 @@ struct console_ops console_ops; | |||
223 | 257 | ||
224 | void start(unsigned long a1, unsigned long a2, void *promptr, void *sp) | 258 | void start(unsigned long a1, unsigned long a2, void *promptr, void *sp) |
225 | { | 259 | { |
260 | struct addr_range vmlinux, initrd; | ||
226 | kernel_entry_t kentry; | 261 | kernel_entry_t kentry; |
227 | char cmdline[COMMAND_LINE_SIZE]; | 262 | char cmdline[COMMAND_LINE_SIZE]; |
228 | unsigned long ft_addr = 0; | 263 | unsigned long ft_addr = 0; |
@@ -242,7 +277,8 @@ void start(unsigned long a1, unsigned long a2, void *promptr, void *sp) | |||
242 | printf("\n\rzImage starting: loaded at 0x%p (sp: 0x%p)\n\r", | 277 | printf("\n\rzImage starting: loaded at 0x%p (sp: 0x%p)\n\r", |
243 | _start, sp); | 278 | _start, sp); |
244 | 279 | ||
245 | prep_kernel(a1, a2); | 280 | vmlinux = prep_kernel(); |
281 | initrd = prep_initrd(vmlinux, a1, a2); | ||
246 | 282 | ||
247 | /* If cmdline came from zimage wrapper or if we can edit the one | 283 | /* If cmdline came from zimage wrapper or if we can edit the one |
248 | * in the dt, print it out and edit it, if possible. | 284 | * in the dt, print it out and edit it, if possible. |
@@ -271,8 +307,7 @@ void start(unsigned long a1, unsigned long a2, void *promptr, void *sp) | |||
271 | if (ft_addr) | 307 | if (ft_addr) |
272 | kentry(ft_addr, 0, NULL); | 308 | kentry(ft_addr, 0, NULL); |
273 | else | 309 | else |
274 | /* XXX initrd addr/size should be passed in properties */ | 310 | kentry((unsigned long)initrd.addr, initrd.size, promptr); |
275 | kentry(initrd.addr, initrd.size, promptr); | ||
276 | 311 | ||
277 | /* console closed so printf below may not work */ | 312 | /* console closed so printf below may not work */ |
278 | printf("Error: Linux kernel returned to zImage boot wrapper!\n\r"); | 313 | printf("Error: Linux kernel returned to zImage boot wrapper!\n\r"); |
diff --git a/arch/powerpc/boot/of.c b/arch/powerpc/boot/of.c index 0182f384f3e6..044f34770b96 100644 --- a/arch/powerpc/boot/of.c +++ b/arch/powerpc/boot/of.c | |||
@@ -208,6 +208,17 @@ static void of_image_hdr(const void *hdr) | |||
208 | } | 208 | } |
209 | } | 209 | } |
210 | 210 | ||
211 | static void *of_vmlinux_alloc(unsigned long size) | ||
212 | { | ||
213 | void *p = malloc(size); | ||
214 | |||
215 | if (!p) { | ||
216 | printf("Can't allocate memory for kernel image!\n\r"); | ||
217 | exit(); | ||
218 | } | ||
219 | return p; | ||
220 | } | ||
221 | |||
211 | static void of_exit(void) | 222 | static void of_exit(void) |
212 | { | 223 | { |
213 | call_prom("exit", 0, 0); | 224 | call_prom("exit", 0, 0); |
@@ -261,6 +272,7 @@ int platform_init(void *promptr, char *dt_blob_start, char *dt_blob_end) | |||
261 | platform_ops.image_hdr = of_image_hdr; | 272 | platform_ops.image_hdr = of_image_hdr; |
262 | platform_ops.malloc = of_try_claim; | 273 | platform_ops.malloc = of_try_claim; |
263 | platform_ops.exit = of_exit; | 274 | platform_ops.exit = of_exit; |
275 | platform_ops.vmlinux_alloc = of_vmlinux_alloc; | ||
264 | 276 | ||
265 | dt_ops.finddevice = of_finddevice; | 277 | dt_ops.finddevice = of_finddevice; |
266 | dt_ops.getprop = of_getprop; | 278 | dt_ops.getprop = of_getprop; |
diff --git a/arch/powerpc/boot/ops.h b/arch/powerpc/boot/ops.h index 8abb6516bb7c..fa62ff223e70 100644 --- a/arch/powerpc/boot/ops.h +++ b/arch/powerpc/boot/ops.h | |||
@@ -25,6 +25,7 @@ struct platform_ops { | |||
25 | void (*free)(void *ptr); | 25 | void (*free)(void *ptr); |
26 | void * (*realloc)(void *ptr, unsigned long size); | 26 | void * (*realloc)(void *ptr, unsigned long size); |
27 | void (*exit)(void); | 27 | void (*exit)(void); |
28 | void * (*vmlinux_alloc)(unsigned long size); | ||
28 | }; | 29 | }; |
29 | extern struct platform_ops platform_ops; | 30 | extern struct platform_ops platform_ops; |
30 | 31 | ||