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