161d2675c7033af58afeb030fc6b258e3ededbd4
[people/xl0/gpxe.git] / src / arch / i386 / image / bzimage.c
1 /*
2  * Copyright (C) 2007 Michael Brown <mbrown@fensystems.co.uk>.
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License as
6  * published by the Free Software Foundation; either version 2 of the
7  * License, or any later version.
8  *
9  * This program is distributed in the hope that 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  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17  */
18
19 /**
20  * @file
21  *
22  * Linux bzImage image format
23  *
24  */
25
26 #include <stdint.h>
27 #include <stdlib.h>
28 #include <string.h>
29 #include <errno.h>
30 #include <assert.h>
31 #include <realmode.h>
32 #include <bzimage.h>
33 #include <gpxe/uaccess.h>
34 #include <gpxe/image.h>
35 #include <gpxe/segment.h>
36 #include <gpxe/init.h>
37 #include <gpxe/initrd.h>
38
39 struct image_type bzimage_image_type __image_type ( PROBE_NORMAL );
40
41 /**
42  * bzImage load context
43  */
44 struct bzimage_load_context {
45         /** Real-mode kernel portion load segment address */
46         unsigned int rm_kernel_seg;
47         /** Real-mode kernel portion load address */
48         userptr_t rm_kernel;
49         /** Real-mode kernel portion file size */
50         size_t rm_filesz;
51         /** Real-mode heap top (offset from rm_kernel) */
52         size_t rm_heap;
53         /** Command line (offset from rm_kernel) */
54         size_t rm_cmdline;
55         /** Real-mode kernel portion total memory size */
56         size_t rm_memsz;
57         /** Non-real-mode kernel portion load address */
58         userptr_t pm_kernel;
59         /** Non-real-mode kernel portion file and memory size */
60         size_t pm_sz;
61 };
62
63 /**
64  * bzImage execution context
65  */
66 struct bzimage_exec_context {
67         /** Real-mode kernel portion load segment address */
68         unsigned int rm_kernel_seg;
69         /** Real-mode kernel portion load address */
70         userptr_t rm_kernel;
71         /** Real-mode heap top (offset from rm_kernel) */
72         size_t rm_heap;
73         /** Command line (offset from rm_kernel) */
74         size_t rm_cmdline;
75         /** Video mode */
76         unsigned int vid_mode;
77         /** Memory limit */
78         uint64_t mem_limit;
79         /** Initrd address */
80         physaddr_t ramdisk_image;
81         /** Initrd size */
82         physaddr_t ramdisk_size;
83 };
84
85 /**
86  * Parse kernel command line for bootloader parameters
87  *
88  * @v image             bzImage file
89  * @v exec_ctx          Execution context
90  * @v cmdline           Kernel command line
91  * @ret rc              Return status code
92  */
93 static int bzimage_parse_cmdline ( struct image *image,
94                                    struct bzimage_exec_context *exec_ctx,
95                                    const char *cmdline ) {
96         char *vga;
97         char *mem;
98
99         /* Look for "vga=" */
100         if ( ( vga = strstr ( cmdline, "vga=" ) ) ) {
101                 vga += 4;
102                 if ( strcmp ( vga, "normal" ) == 0 ) {
103                         exec_ctx->vid_mode = BZI_VID_MODE_NORMAL;
104                 } else if ( strcmp ( vga, "ext" ) == 0 ) {
105                         exec_ctx->vid_mode = BZI_VID_MODE_EXT;
106                 } else if ( strcmp ( vga, "ask" ) == 0 ) {
107                         exec_ctx->vid_mode = BZI_VID_MODE_ASK;
108                 } else {
109                         exec_ctx->vid_mode = strtoul ( vga, &vga, 0 );
110                         if ( *vga && ( *vga != ' ' ) ) {
111                                 DBGC ( image, "bzImage %p strange \"vga=\""
112                                        "terminator '%c'\n", image, *vga );
113                         }
114                 }
115         }
116
117         /* Look for "mem=" */
118         if ( ( mem = strstr ( cmdline, "mem=" ) ) ) {
119                 mem += 4;
120                 exec_ctx->mem_limit = strtoul ( mem, &mem, 0 );
121                 switch ( *mem ) {
122                 case 'G':
123                 case 'g':
124                         exec_ctx->mem_limit <<= 10;
125                 case 'M':
126                 case 'm':
127                         exec_ctx->mem_limit <<= 10;
128                 case 'K':
129                 case 'k':
130                         exec_ctx->mem_limit <<= 10;
131                         break;
132                 case '\0':
133                 case ' ':
134                         break;
135                 default:
136                         DBGC ( image, "bzImage %p strange \"mem=\" "
137                                "terminator '%c'\n", image, *mem );
138                         break;
139                 }
140         }
141
142         return 0;
143 }
144
145 /**
146  * Set command line
147  *
148  * @v image             bzImage image
149  * @v exec_ctx          Execution context
150  * @v cmdline           Kernel command line
151  * @ret rc              Return status code
152  */
153 static int bzimage_set_cmdline ( struct image *image,
154                                  struct bzimage_exec_context *exec_ctx,
155                                  const char *cmdline ) {
156         size_t cmdline_len;
157
158         /* Copy command line down to real-mode portion */
159         cmdline_len = ( strlen ( cmdline ) + 1 );
160         if ( cmdline_len > BZI_CMDLINE_SIZE )
161                 cmdline_len = BZI_CMDLINE_SIZE;
162         copy_to_user ( exec_ctx->rm_kernel, exec_ctx->rm_cmdline,
163                        cmdline, cmdline_len );
164         DBGC ( image, "bzImage %p command line \"%s\"\n", image, cmdline );
165
166         return 0;
167 }
168
169 /**
170  * Load initrds, if any
171  *
172  * @v image             bzImage image
173  * @v exec_ctx          Execution context
174  * @ret rc              Return status code
175  */
176 static int bzimage_load_initrd ( struct image *image,
177                                  struct bzimage_exec_context *exec_ctx ) {
178         struct image *initrd;
179         size_t initrd_len;
180         size_t total_len = 0;
181         size_t offset = 0;
182         physaddr_t start;
183         int rc;
184
185         /* Add up length of all initrd images */
186         for_each_image ( initrd ) {
187                 if ( initrd->type != &initrd_image_type )
188                         continue;
189                 initrd_len = ( ( image->len + 0x0f ) & ~0x0f );
190                 total_len += initrd_len;
191         }
192
193         if ( ! total_len )
194                 return 0;
195
196         /* Find a suitable start address.  Try 1MB boundaries,
197          * starting from the downloaded kernel image itself and
198          * working downwards until we hit an available region.
199          */
200         for ( start = ( user_to_phys ( image->data, 0 ) & ~0xfffff ) ; ;
201               start -= 0x100000 ) {
202                 /* Check that we're not going to overwrite the
203                  * kernel itself.  This check isn't totally
204                  * accurate, but errs on the side of caution.
205                  */
206                 if ( start <= ( BZI_LOAD_HIGH_ADDR + image->len ) ) {
207                         DBGC ( image, "bzImage %p could not find a location "
208                                "for initrd\n", image );
209                         return -ENOBUFS;
210                 }
211                 /* Check that we are within the kernel's range */
212                 if ( ( start + total_len ) > exec_ctx->mem_limit )
213                         continue;
214                 /* Prepare and verify segment */
215                 if ( ( rc = prep_segment ( phys_to_user ( start ), 0,
216                                            total_len ) ) != 0 )
217                         continue;
218                 /* Use this address */
219                 break;
220         }
221
222         /* Construct initrd */
223         DBGC ( image, "bzImage %p constructing initrd at [%lx,%lx)\n",
224                image, start, ( start + total_len ) );
225         for_each_image ( initrd ) {
226                 if ( initrd->type != &initrd_image_type )
227                         continue;
228                 initrd_len = ( ( image->len + 0x0f ) & ~0x0f );
229                 DBGC ( image, "bzImage %p has initrd %p at [%lx,%lx)\n",
230                        image, initrd, ( start + offset ),
231                        ( start + offset + initrd->len ) );
232                 memcpy_user ( phys_to_user ( start ), offset,
233                               initrd->data, 0, initrd->len );
234                 offset += initrd_len;
235         }
236         assert ( offset == total_len );
237
238         /* Record initrd location */
239         exec_ctx->ramdisk_image = start;
240         exec_ctx->ramdisk_size = total_len;
241
242         return 0;
243 }
244
245 /**
246  * Execute bzImage image
247  *
248  * @v image             bzImage image
249  * @ret rc              Return status code
250  */
251 static int bzimage_exec ( struct image *image ) {
252         struct bzimage_exec_context exec_ctx;
253         struct bzimage_header bzhdr;
254         const char *cmdline = ( image->cmdline ? image->cmdline : "" );
255         int rc;
256
257         /* Initialise context */
258         memset ( &exec_ctx, 0, sizeof ( exec_ctx ) );
259
260         /* Retrieve kernel header */
261         exec_ctx.rm_kernel_seg = image->priv.ul;
262         exec_ctx.rm_kernel = real_to_user ( exec_ctx.rm_kernel_seg, 0 );
263         copy_from_user ( &bzhdr, exec_ctx.rm_kernel, BZI_HDR_OFFSET,
264                          sizeof ( bzhdr ) );
265         exec_ctx.rm_cmdline = exec_ctx.rm_heap = 
266                 ( bzhdr.heap_end_ptr + 0x200 );
267         exec_ctx.vid_mode = bzhdr.vid_mode;
268         if ( bzhdr.version >= 0x0203 ) {
269                 exec_ctx.mem_limit = ( bzhdr.initrd_addr_max + 1 );
270         } else {
271                 exec_ctx.mem_limit = ( BZI_INITRD_MAX + 1 );
272         }
273
274         /* Parse command line for bootloader parameters */
275         if ( ( rc = bzimage_parse_cmdline ( image, &exec_ctx, cmdline ) ) != 0)
276                 return rc;
277
278         /* Store command line */
279         if ( ( rc = bzimage_set_cmdline ( image, &exec_ctx, cmdline ) ) != 0 )
280                 return rc;
281
282         /* Load any initrds */
283         if ( ( rc = bzimage_load_initrd ( image, &exec_ctx ) ) != 0 )
284                 return rc;
285
286         /* Update and store kernel header */
287         bzhdr.vid_mode = exec_ctx.vid_mode;
288         bzhdr.ramdisk_image = exec_ctx.ramdisk_image;
289         bzhdr.ramdisk_size = exec_ctx.ramdisk_size;
290         copy_to_user ( exec_ctx.rm_kernel, BZI_HDR_OFFSET, &bzhdr,
291                        sizeof ( bzhdr ) );
292
293         /* Prepare for exiting */
294         shutdown();
295
296         /* Jump to the kernel */
297         __asm__ __volatile__ ( REAL_CODE ( "movw %w0, %%ds\n\t"
298                                            "movw %w0, %%es\n\t"
299                                            "movw %w0, %%fs\n\t"
300                                            "movw %w0, %%gs\n\t"
301                                            "movw %w0, %%ss\n\t"
302                                            "movw %w1, %%sp\n\t"
303                                            "pushw %w2\n\t"
304                                            "pushw $0\n\t"
305                                            "lret\n\t" )
306                                : : "r" ( exec_ctx.rm_kernel_seg ),
307                                    "r" ( exec_ctx.rm_heap ),
308                                    "r" ( exec_ctx.rm_kernel_seg + 0x20 ) );
309
310         /* There is no way for the image to return, since we provide
311          * no return address.
312          */
313         assert ( 0 );
314
315         return -ECANCELED; /* -EIMPOSSIBLE */
316 }
317
318 /**
319  * Load and parse bzImage header
320  *
321  * @v image             bzImage file
322  * @v load_ctx          Load context
323  * @v bzhdr             Buffer for bzImage header
324  * @ret rc              Return status code
325  */
326 static int bzimage_load_header ( struct image *image,
327                                  struct bzimage_load_context *load_ctx,
328                                  struct bzimage_header *bzhdr ) {
329
330         /* Sanity check */
331         if ( image->len < ( BZI_HDR_OFFSET + sizeof ( *bzhdr ) ) ) {
332                 DBGC ( image, "bzImage %p too short for kernel header\n",
333                        image );
334                 return -ENOEXEC;
335         }
336
337         /* Read and verify header */
338         copy_from_user ( bzhdr, image->data, BZI_HDR_OFFSET,
339                          sizeof ( *bzhdr ) );
340         if ( bzhdr->header != BZI_SIGNATURE ) {
341                 DBGC ( image, "bzImage %p bad signature %08lx\n",
342                        image, bzhdr->header );
343                 return -ENOEXEC;
344         }
345
346         /* We don't support ancient kernels */
347         if ( bzhdr->version < 0x0200 ) {
348                 DBGC ( image, "bzImage %p version %04x not supported\n",
349                        image, bzhdr->version );
350                 return -ENOTSUP;
351         }
352
353         /* Calculate load address and size of real-mode portion */
354         load_ctx->rm_kernel_seg = 0x1000; /* place RM kernel at 1000:0000 */
355         load_ctx->rm_kernel = real_to_user ( load_ctx->rm_kernel_seg, 0 );
356         load_ctx->rm_filesz = load_ctx->rm_memsz =
357                 ( ( bzhdr->setup_sects ? bzhdr->setup_sects : 4 ) + 1 ) << 9;
358         if ( load_ctx->rm_filesz > image->len ) {
359                 DBGC ( image, "bzImage %p too short for %zd byte of setup\n",
360                        image, load_ctx->rm_filesz );
361                 return -ENOEXEC;
362         }
363
364         /* Calculate load address and size of non-real-mode portion */
365         load_ctx->pm_kernel = ( ( bzhdr->loadflags & BZI_LOAD_HIGH ) ?
366                                 phys_to_user ( BZI_LOAD_HIGH_ADDR ) :
367                                 phys_to_user ( BZI_LOAD_LOW_ADDR ) );
368         load_ctx->pm_sz = ( image->len - load_ctx->rm_filesz );
369
370         DBGC ( image, "bzImage %p version %04x RM %#zx bytes PM %#zx bytes\n",
371                image, bzhdr->version, load_ctx->rm_filesz, load_ctx->pm_sz );
372         return 0;
373 }
374
375 /**
376  * Load real-mode portion of bzImage
377  *
378  * @v image             bzImage file
379  * @v load_ctx          Load context
380  * @ret rc              Return status code
381  */
382 static int bzimage_load_real ( struct image *image,
383                                struct bzimage_load_context *load_ctx ) {
384         int rc;
385
386         /* Allow space for the stack and heap */
387         load_ctx->rm_memsz += BZI_STACK_SIZE;
388         load_ctx->rm_heap = load_ctx->rm_memsz;
389
390         /* Allow space for the command line */
391         load_ctx->rm_cmdline = load_ctx->rm_memsz;
392         load_ctx->rm_memsz += BZI_CMDLINE_SIZE;
393
394         /* Prepare, verify, and load the real-mode segment */
395         if ( ( rc = prep_segment ( load_ctx->rm_kernel, load_ctx->rm_filesz,
396                                    load_ctx->rm_memsz ) ) != 0 ) {
397                 DBGC ( image, "bzImage %p could not prepare RM segment: %s\n",
398                        image, strerror ( rc ) );
399                 return rc;
400         }
401         memcpy_user ( load_ctx->rm_kernel, 0, image->data, 0,
402                       load_ctx->rm_filesz );
403
404         return 0;
405 }
406
407 /**
408  * Load non-real-mode portion of bzImage
409  *
410  * @v image             bzImage file
411  * @v load_ctx          Load context
412  * @ret rc              Return status code
413  */
414 static int bzimage_load_non_real ( struct image *image,
415                                    struct bzimage_load_context *load_ctx ) {
416         int rc;
417
418         /* Prepare, verify and load the non-real-mode segment */
419         if ( ( rc = prep_segment ( load_ctx->pm_kernel, load_ctx->pm_sz,
420                                    load_ctx->pm_sz ) ) != 0 ) {
421                 DBGC ( image, "bzImage %p could not prepare PM segment: %s\n",
422                        image, strerror ( rc ) );
423                 return rc;
424         }
425         memcpy_user ( load_ctx->pm_kernel, 0, image->data, load_ctx->rm_filesz,
426                       load_ctx->pm_sz );
427
428         return 0;
429 }
430
431 /**
432  * Update and store bzImage header
433  *
434  * @v image             bzImage file
435  * @v load_ctx          Load context
436  * @v bzhdr             Original bzImage header
437  * @ret rc              Return status code
438  */
439 static int bzimage_write_header ( struct image *image __unused,
440                                   struct bzimage_load_context *load_ctx,
441                                   struct bzimage_header *bzhdr ) {
442         struct bzimage_cmdline cmdline;
443
444         /* Update the header and copy it into the loaded kernel */
445         bzhdr->type_of_loader = BZI_LOADER_TYPE_GPXE;
446         if ( bzhdr->version >= 0x0201 ) {
447                 bzhdr->heap_end_ptr = ( load_ctx->rm_heap - 0x200 );
448                 bzhdr->loadflags |= BZI_CAN_USE_HEAP;
449         }
450         if ( bzhdr->version >= 0x0202 ) {
451                 bzhdr->cmd_line_ptr = user_to_phys ( load_ctx->rm_kernel,
452                                                      load_ctx->rm_cmdline );
453         } else {
454                 cmdline.magic = BZI_CMDLINE_MAGIC;
455                 cmdline.offset = load_ctx->rm_cmdline;
456                 copy_to_user ( load_ctx->rm_kernel, BZI_CMDLINE_OFFSET,
457                                &cmdline, sizeof ( cmdline ) );
458                 bzhdr->setup_move_size = load_ctx->rm_memsz;
459         }
460         copy_to_user ( load_ctx->rm_kernel, BZI_HDR_OFFSET,
461                        bzhdr, sizeof ( *bzhdr ) );
462
463         return 0;
464 }
465
466 /**
467  * Load bzImage image into memory
468  *
469  * @v image             bzImage file
470  * @ret rc              Return status code
471  */
472 int bzimage_load ( struct image *image ) {
473         struct bzimage_load_context load_ctx;
474         struct bzimage_header bzhdr;
475         int rc;
476
477         /* Initialise context */
478         memset ( &load_ctx, 0, sizeof ( load_ctx ) );
479
480         /* Load and verify header */
481         if ( ( rc = bzimage_load_header ( image, &load_ctx, &bzhdr ) ) != 0 )
482                 return rc;
483
484         /* This is a bzImage image, valid or otherwise */
485         if ( ! image->type )
486                 image->type = &bzimage_image_type;
487
488         /* Load real-mode portion */
489         if ( ( rc = bzimage_load_real ( image, &load_ctx ) ) != 0 )
490                 return rc;
491
492         /* Load non-real-mode portion */
493         if ( ( rc = bzimage_load_non_real ( image, &load_ctx ) ) != 0 )
494                 return rc;
495
496         /* Update and write out header */
497         if ( ( rc = bzimage_write_header ( image, &load_ctx, &bzhdr ) ) != 0 )
498                 return rc;
499
500         /* Record real-mode segment in image private data field */
501         image->priv.ul = load_ctx.rm_kernel_seg;
502
503         return 0;
504 }
505
506 /** Linux bzImage image type */
507 struct image_type bzimage_image_type __image_type ( PROBE_NORMAL ) = {
508         .name = "bzImage",
509         .load = bzimage_load,
510         .exec = bzimage_exec,
511 };