Add code for constructing single-file cpio archives on the fly
[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/init.h>
37 #include <gpxe/initrd.h>
38 #include <gpxe/cpio.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
172  *
173  * @v image             bzImage image
174  * @v initrd            initrd image
175  * @v address           Address at which to load, or UNULL
176  * @ret len             Length of loaded image, rounded up to 4 bytes
177  */
178 static size_t bzimage_load_initrd ( struct image *image,
179                                     struct image *initrd,
180                                     userptr_t address ) {
181         char *filename = initrd->cmdline;
182         struct cpio_header cpio;
183         size_t offset = 0;
184
185         /* Ignore images which aren't initrds */
186         if ( initrd->type != &initrd_image_type )
187                 return 0;
188
189         /* Create cpio header before non-prebuilt images */
190         if ( filename[0] ) {
191                 size_t name_len = ( strlen ( filename ) + 1 );
192
193                 DBGC ( image, "bzImage %p inserting initrd %p as %s\n",
194                        image, initrd, filename );
195                 memset ( &cpio, '0', sizeof ( cpio ) );
196                 memcpy ( cpio.c_magic, CPIO_MAGIC, sizeof ( cpio.c_magic ) );
197                 cpio_set_field ( cpio.c_mode, 0100644 );
198                 cpio_set_field ( cpio.c_nlink, 1 );
199                 cpio_set_field ( cpio.c_filesize, initrd->len );
200                 cpio_set_field ( cpio.c_namesize, name_len );
201                 if ( address ) {
202                         copy_to_user ( address, offset, &cpio,
203                                        sizeof ( cpio ) );
204                 }
205                 offset += sizeof ( cpio );
206                 if ( address ) {
207                         copy_to_user ( address, offset, filename,
208                                        name_len );
209                 }
210                 offset += name_len;
211                 offset = ( ( offset + 0x03 ) & ~0x03 );
212         }
213
214         /* Copy in initrd image body */
215         if ( address ) {
216                 DBGC ( image, "bzImage %p has initrd %p at [%lx,%lx)\n",
217                        image, initrd, address, ( address + offset ) );
218                 memcpy_user ( address, offset, initrd->data, 0,
219                               initrd->len );
220         }
221         offset += initrd->len;
222
223         offset = ( ( offset + 0x03 ) & ~0x03 );
224         return offset;
225 }
226
227 /**
228  * Load initrds, if any
229  *
230  * @v image             bzImage image
231  * @v exec_ctx          Execution context
232  * @ret rc              Return status code
233  */
234 static int bzimage_load_initrds ( struct image *image,
235                                   struct bzimage_exec_context *exec_ctx ) {
236         struct image *initrd;
237         size_t total_len = 0;
238         physaddr_t address;
239         int rc;
240
241         /* Add up length of all initrd images */
242         for_each_image ( initrd ) {
243                 total_len += bzimage_load_initrd ( image, initrd, UNULL );
244         }
245
246         /* Give up if no initrd images found */
247         if ( ! total_len )
248                 return 0;
249
250         /* Find a suitable start address.  Try 1MB boundaries,
251          * starting from the downloaded kernel image itself and
252          * working downwards until we hit an available region.
253          */
254         for ( address = ( user_to_phys ( image->data, 0 ) & ~0xfffff ) ; ;
255               address -= 0x100000 ) {
256                 /* Check that we're not going to overwrite the
257                  * kernel itself.  This check isn't totally
258                  * accurate, but errs on the side of caution.
259                  */
260                 if ( address <= ( BZI_LOAD_HIGH_ADDR + image->len ) ) {
261                         DBGC ( image, "bzImage %p could not find a location "
262                                "for initrd\n", image );
263                         return -ENOBUFS;
264                 }
265                 /* Check that we are within the kernel's range */
266                 if ( ( address + total_len ) > exec_ctx->mem_limit )
267                         continue;
268                 /* Prepare and verify segment */
269                 if ( ( rc = prep_segment ( phys_to_user ( address ), 0,
270                                            total_len ) ) != 0 )
271                         continue;
272                 /* Use this address */
273                 break;
274         }
275
276         /* Record initrd location */
277         exec_ctx->ramdisk_image = address;
278         exec_ctx->ramdisk_size = total_len;
279
280         /* Construct initrd */
281         DBGC ( image, "bzImage %p constructing initrd at [%lx,%lx)\n",
282                image, address, ( address + total_len ) );
283         for_each_image ( initrd ) {
284                 address += bzimage_load_initrd ( image, initrd,
285                                                  phys_to_user ( address ) );
286         }
287
288         return 0;
289 }
290
291 /**
292  * Execute bzImage image
293  *
294  * @v image             bzImage image
295  * @ret rc              Return status code
296  */
297 static int bzimage_exec ( struct image *image ) {
298         struct bzimage_exec_context exec_ctx;
299         struct bzimage_header bzhdr;
300         const char *cmdline = ( image->cmdline ? image->cmdline : "" );
301         int rc;
302
303         /* Initialise context */
304         memset ( &exec_ctx, 0, sizeof ( exec_ctx ) );
305
306         /* Retrieve kernel header */
307         exec_ctx.rm_kernel_seg = image->priv.ul;
308         exec_ctx.rm_kernel = real_to_user ( exec_ctx.rm_kernel_seg, 0 );
309         copy_from_user ( &bzhdr, exec_ctx.rm_kernel, BZI_HDR_OFFSET,
310                          sizeof ( bzhdr ) );
311         exec_ctx.rm_cmdline = exec_ctx.rm_heap = 
312                 ( bzhdr.heap_end_ptr + 0x200 );
313         exec_ctx.vid_mode = bzhdr.vid_mode;
314         if ( bzhdr.version >= 0x0203 ) {
315                 exec_ctx.mem_limit = ( bzhdr.initrd_addr_max + 1 );
316         } else {
317                 exec_ctx.mem_limit = ( BZI_INITRD_MAX + 1 );
318         }
319
320         /* Parse command line for bootloader parameters */
321         if ( ( rc = bzimage_parse_cmdline ( image, &exec_ctx, cmdline ) ) != 0)
322                 return rc;
323
324         /* Store command line */
325         if ( ( rc = bzimage_set_cmdline ( image, &exec_ctx, cmdline ) ) != 0 )
326                 return rc;
327
328         /* Load any initrds */
329         if ( ( rc = bzimage_load_initrds ( image, &exec_ctx ) ) != 0 )
330                 return rc;
331
332         /* Update and store kernel header */
333         bzhdr.vid_mode = exec_ctx.vid_mode;
334         bzhdr.ramdisk_image = exec_ctx.ramdisk_image;
335         bzhdr.ramdisk_size = exec_ctx.ramdisk_size;
336         copy_to_user ( exec_ctx.rm_kernel, BZI_HDR_OFFSET, &bzhdr,
337                        sizeof ( bzhdr ) );
338
339         /* Prepare for exiting */
340         shutdown();
341
342         /* Jump to the kernel */
343         __asm__ __volatile__ ( REAL_CODE ( "movw %w0, %%ds\n\t"
344                                            "movw %w0, %%es\n\t"
345                                            "movw %w0, %%fs\n\t"
346                                            "movw %w0, %%gs\n\t"
347                                            "movw %w0, %%ss\n\t"
348                                            "movw %w1, %%sp\n\t"
349                                            "pushw %w2\n\t"
350                                            "pushw $0\n\t"
351                                            "lret\n\t" )
352                                : : "r" ( exec_ctx.rm_kernel_seg ),
353                                    "r" ( exec_ctx.rm_heap ),
354                                    "r" ( exec_ctx.rm_kernel_seg + 0x20 ) );
355
356         /* There is no way for the image to return, since we provide
357          * no return address.
358          */
359         assert ( 0 );
360
361         return -ECANCELED; /* -EIMPOSSIBLE */
362 }
363
364 /**
365  * Load and parse bzImage header
366  *
367  * @v image             bzImage file
368  * @v load_ctx          Load context
369  * @v bzhdr             Buffer for bzImage header
370  * @ret rc              Return status code
371  */
372 static int bzimage_load_header ( struct image *image,
373                                  struct bzimage_load_context *load_ctx,
374                                  struct bzimage_header *bzhdr ) {
375
376         /* Sanity check */
377         if ( image->len < ( BZI_HDR_OFFSET + sizeof ( *bzhdr ) ) ) {
378                 DBGC ( image, "bzImage %p too short for kernel header\n",
379                        image );
380                 return -ENOEXEC;
381         }
382
383         /* Read and verify header */
384         copy_from_user ( bzhdr, image->data, BZI_HDR_OFFSET,
385                          sizeof ( *bzhdr ) );
386         if ( bzhdr->header != BZI_SIGNATURE ) {
387                 DBGC ( image, "bzImage %p bad signature %08lx\n",
388                        image, bzhdr->header );
389                 return -ENOEXEC;
390         }
391
392         /* We don't support ancient kernels */
393         if ( bzhdr->version < 0x0200 ) {
394                 DBGC ( image, "bzImage %p version %04x not supported\n",
395                        image, bzhdr->version );
396                 return -ENOTSUP;
397         }
398
399         /* Calculate load address and size of real-mode portion */
400         load_ctx->rm_kernel_seg = 0x1000; /* place RM kernel at 1000:0000 */
401         load_ctx->rm_kernel = real_to_user ( load_ctx->rm_kernel_seg, 0 );
402         load_ctx->rm_filesz = load_ctx->rm_memsz =
403                 ( ( bzhdr->setup_sects ? bzhdr->setup_sects : 4 ) + 1 ) << 9;
404         if ( load_ctx->rm_filesz > image->len ) {
405                 DBGC ( image, "bzImage %p too short for %zd byte of setup\n",
406                        image, load_ctx->rm_filesz );
407                 return -ENOEXEC;
408         }
409
410         /* Calculate load address and size of non-real-mode portion */
411         load_ctx->pm_kernel = ( ( bzhdr->loadflags & BZI_LOAD_HIGH ) ?
412                                 phys_to_user ( BZI_LOAD_HIGH_ADDR ) :
413                                 phys_to_user ( BZI_LOAD_LOW_ADDR ) );
414         load_ctx->pm_sz = ( image->len - load_ctx->rm_filesz );
415
416         DBGC ( image, "bzImage %p version %04x RM %#zx bytes PM %#zx bytes\n",
417                image, bzhdr->version, load_ctx->rm_filesz, load_ctx->pm_sz );
418         return 0;
419 }
420
421 /**
422  * Load real-mode portion of bzImage
423  *
424  * @v image             bzImage file
425  * @v load_ctx          Load context
426  * @ret rc              Return status code
427  */
428 static int bzimage_load_real ( struct image *image,
429                                struct bzimage_load_context *load_ctx ) {
430         int rc;
431
432         /* Allow space for the stack and heap */
433         load_ctx->rm_memsz += BZI_STACK_SIZE;
434         load_ctx->rm_heap = load_ctx->rm_memsz;
435
436         /* Allow space for the command line */
437         load_ctx->rm_cmdline = load_ctx->rm_memsz;
438         load_ctx->rm_memsz += BZI_CMDLINE_SIZE;
439
440         /* Prepare, verify, and load the real-mode segment */
441         if ( ( rc = prep_segment ( load_ctx->rm_kernel, load_ctx->rm_filesz,
442                                    load_ctx->rm_memsz ) ) != 0 ) {
443                 DBGC ( image, "bzImage %p could not prepare RM segment: %s\n",
444                        image, strerror ( rc ) );
445                 return rc;
446         }
447         memcpy_user ( load_ctx->rm_kernel, 0, image->data, 0,
448                       load_ctx->rm_filesz );
449
450         return 0;
451 }
452
453 /**
454  * Load non-real-mode portion of bzImage
455  *
456  * @v image             bzImage file
457  * @v load_ctx          Load context
458  * @ret rc              Return status code
459  */
460 static int bzimage_load_non_real ( struct image *image,
461                                    struct bzimage_load_context *load_ctx ) {
462         int rc;
463
464         /* Prepare, verify and load the non-real-mode segment */
465         if ( ( rc = prep_segment ( load_ctx->pm_kernel, load_ctx->pm_sz,
466                                    load_ctx->pm_sz ) ) != 0 ) {
467                 DBGC ( image, "bzImage %p could not prepare PM segment: %s\n",
468                        image, strerror ( rc ) );
469                 return rc;
470         }
471         memcpy_user ( load_ctx->pm_kernel, 0, image->data, load_ctx->rm_filesz,
472                       load_ctx->pm_sz );
473
474         return 0;
475 }
476
477 /**
478  * Update and store bzImage header
479  *
480  * @v image             bzImage file
481  * @v load_ctx          Load context
482  * @v bzhdr             Original bzImage header
483  * @ret rc              Return status code
484  */
485 static int bzimage_write_header ( struct image *image __unused,
486                                   struct bzimage_load_context *load_ctx,
487                                   struct bzimage_header *bzhdr ) {
488         struct bzimage_cmdline cmdline;
489
490         /* Update the header and copy it into the loaded kernel */
491         bzhdr->type_of_loader = BZI_LOADER_TYPE_GPXE;
492         if ( bzhdr->version >= 0x0201 ) {
493                 bzhdr->heap_end_ptr = ( load_ctx->rm_heap - 0x200 );
494                 bzhdr->loadflags |= BZI_CAN_USE_HEAP;
495         }
496         if ( bzhdr->version >= 0x0202 ) {
497                 bzhdr->cmd_line_ptr = user_to_phys ( load_ctx->rm_kernel,
498                                                      load_ctx->rm_cmdline );
499         } else {
500                 cmdline.magic = BZI_CMDLINE_MAGIC;
501                 cmdline.offset = load_ctx->rm_cmdline;
502                 copy_to_user ( load_ctx->rm_kernel, BZI_CMDLINE_OFFSET,
503                                &cmdline, sizeof ( cmdline ) );
504                 bzhdr->setup_move_size = load_ctx->rm_memsz;
505         }
506         copy_to_user ( load_ctx->rm_kernel, BZI_HDR_OFFSET,
507                        bzhdr, sizeof ( *bzhdr ) );
508
509         return 0;
510 }
511
512 /**
513  * Load bzImage image into memory
514  *
515  * @v image             bzImage file
516  * @ret rc              Return status code
517  */
518 int bzimage_load ( struct image *image ) {
519         struct bzimage_load_context load_ctx;
520         struct bzimage_header bzhdr;
521         int rc;
522
523         /* Initialise context */
524         memset ( &load_ctx, 0, sizeof ( load_ctx ) );
525
526         /* Load and verify header */
527         if ( ( rc = bzimage_load_header ( image, &load_ctx, &bzhdr ) ) != 0 )
528                 return rc;
529
530         /* This is a bzImage image, valid or otherwise */
531         if ( ! image->type )
532                 image->type = &bzimage_image_type;
533
534         /* Load real-mode portion */
535         if ( ( rc = bzimage_load_real ( image, &load_ctx ) ) != 0 )
536                 return rc;
537
538         /* Load non-real-mode portion */
539         if ( ( rc = bzimage_load_non_real ( image, &load_ctx ) ) != 0 )
540                 return rc;
541
542         /* Update and write out header */
543         if ( ( rc = bzimage_write_header ( image, &load_ctx, &bzhdr ) ) != 0 )
544                 return rc;
545
546         /* Record real-mode segment in image private data field */
547         image->priv.ul = load_ctx.rm_kernel_seg;
548
549         return 0;
550 }
551
552 /** Linux bzImage image type */
553 struct image_type bzimage_image_type __image_type ( PROBE_NORMAL ) = {
554         .name = "bzImage",
555         .load = bzimage_load,
556         .exec = bzimage_exec,
557 };