2 * GRUB -- GRand Unified Bootloader
3 * Copyright (C) 2006,2007 Free Software Foundation, Inc.
5 * GRUB is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
10 * GRUB is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with GRUB. If not, see <http://www.gnu.org/licenses/>.
19 #include <grub/loader.h>
20 #include <grub/machine/loader.h>
21 #include <grub/file.h>
22 #include <grub/disk.h>
24 #include <grub/misc.h>
25 #include <grub/types.h>
26 #include <grub/rescue.h>
29 #include <grub/term.h>
30 #include <grub/cpu/linux.h>
31 #include <grub/efi/api.h>
32 #include <grub/efi/efi.h>
34 #define NEXT_MEMORY_DESCRIPTOR(desc, size) \
35 ((grub_efi_memory_descriptor_t *) ((char *) (desc) + (size)))
37 static grub_dl_t my_mod
;
39 static grub_size_t linux_mem_size
;
41 static void *real_mode_mem
;
42 static void *prot_mode_mem
;
43 static void *initrd_mem
;
44 static grub_efi_uintn_t real_mode_pages
;
45 static grub_efi_uintn_t prot_mode_pages
;
46 static grub_efi_uintn_t initrd_pages
;
47 static void *mmap_buf
;
49 static grub_uint8_t gdt
[] __attribute__ ((aligned(16))) =
52 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
54 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
56 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x9A, 0xCF, 0x00,
58 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x92, 0xCF, 0x00
66 } __attribute__ ((aligned(4), packed
));
68 static struct gdt_descriptor gdt_desc
=
80 } __attribute__ ((aligned(4)));
82 static struct idt_descriptor idt_desc
=
89 static inline grub_size_t
90 page_align (grub_size_t size
)
92 return (size
+ (1 << 12) - 1) & (~((1 << 12) - 1));
95 /* Find the optimal number of pages for the memory map. Is it better to
96 move this code to efi/mm.c? */
97 static grub_efi_uintn_t
100 static grub_efi_uintn_t mmap_size
= 0;
105 mmap_size
= (1 << 12);
109 grub_efi_memory_descriptor_t
*mmap
;
110 grub_efi_uintn_t desc_size
;
112 mmap
= grub_malloc (mmap_size
);
116 ret
= grub_efi_get_memory_map (&mmap_size
, mmap
, 0, &desc_size
, 0);
120 grub_fatal ("cannot get memory map");
124 mmap_size
+= (1 << 12);
127 /* Increase the size a bit for safety, because GRUB allocates more on
128 later, and EFI itself may allocate more. */
129 mmap_size
+= (1 << 12);
131 return page_align (mmap_size
);
139 grub_efi_free_pages ((grub_addr_t
) real_mode_mem
, real_mode_pages
);
145 grub_efi_free_pages ((grub_addr_t
) prot_mode_mem
, prot_mode_pages
);
151 grub_efi_free_pages ((grub_addr_t
) initrd_mem
, initrd_pages
);
156 /* Allocate pages for the real mode code and the protected mode code
157 for linux as well as a memory map buffer. */
159 allocate_pages (grub_size_t real_size
, grub_size_t prot_size
)
161 grub_efi_uintn_t desc_size
;
162 grub_efi_memory_descriptor_t
*mmap
, *mmap_end
;
163 grub_efi_uintn_t mmap_size
, tmp_mmap_size
;
164 grub_efi_memory_descriptor_t
*desc
;
166 /* Make sure that each size is aligned to a page boundary. */
167 real_size
= page_align (real_size
+ GRUB_DISK_SECTOR_SIZE
);
168 prot_size
= page_align (prot_size
);
169 mmap_size
= find_mmap_size ();
171 grub_dprintf ("linux", "real_size = %x, prot_size = %x, mmap_size = %x\n",
172 real_size
, prot_size
, mmap_size
);
174 /* Calculate the number of pages; Combine the real mode code with
175 the memory map buffer for simplicity. */
176 real_mode_pages
= ((real_size
+ mmap_size
) >> 12);
177 prot_mode_pages
= (prot_size
>> 12);
179 /* Initialize the memory pointers with NULL for convenience. */
183 /* Read the memory map temporarily, to find free space. */
184 mmap
= grub_malloc (mmap_size
);
188 tmp_mmap_size
= mmap_size
;
189 if (grub_efi_get_memory_map (&tmp_mmap_size
, mmap
, 0, &desc_size
, 0) <= 0)
190 grub_fatal ("cannot get memory map");
192 mmap_end
= NEXT_MEMORY_DESCRIPTOR (mmap
, tmp_mmap_size
);
194 /* First, find free pages for the real mode code
195 and the memory map buffer. */
198 desc
= NEXT_MEMORY_DESCRIPTOR (desc
, desc_size
))
200 /* Probably it is better to put the real mode code in the traditional
202 if (desc
->type
== GRUB_EFI_CONVENTIONAL_MEMORY
203 && desc
->physical_start
<= 0x90000
204 && desc
->num_pages
>= real_mode_pages
)
206 grub_efi_physical_address_t physical_end
;
207 grub_efi_physical_address_t addr
;
209 physical_end
= desc
->physical_start
+ (desc
->num_pages
<< 12);
210 if (physical_end
> 0x90000)
211 physical_end
= 0x90000;
213 grub_dprintf ("linux", "physical_start = %x, physical_end = %x\n",
214 (unsigned) desc
->physical_start
,
215 (unsigned) physical_end
);
216 addr
= physical_end
- real_size
- mmap_size
;
220 grub_dprintf ("linux", "trying to allocate %u pages at %x\n",
221 real_mode_pages
, (unsigned) addr
);
222 real_mode_mem
= grub_efi_allocate_pages (addr
, real_mode_pages
);
224 grub_fatal ("cannot allocate pages");
226 desc
->num_pages
-= real_mode_pages
;
233 grub_error (GRUB_ERR_OUT_OF_MEMORY
, "cannot allocate real mode pages");
237 mmap_buf
= (void *) ((char *) real_mode_mem
+ real_size
);
239 /* Next, find free pages for the protected mode code. */
240 /* XXX what happens if anything is using this address? */
241 prot_mode_mem
= grub_efi_allocate_pages (0x100000, prot_mode_pages
);
244 grub_error (GRUB_ERR_OUT_OF_MEMORY
,
245 "cannot allocate protected mode pages");
259 grub_linux_boot (void)
261 struct linux_kernel_header
*lh
;
262 struct linux_kernel_params
*params
;
263 grub_efi_uintn_t mmap_size
;
264 grub_efi_uintn_t map_key
;
265 grub_efi_uintn_t desc_size
;
266 grub_efi_uint32_t desc_version
;
269 params
= real_mode_mem
;
271 grub_dprintf ("linux", "code32_start = %x, idt_desc = %x, gdt_desc = %x\n",
272 (unsigned) lh
->code32_start
, (grub_addr_t
) &(idt_desc
.limit
),
273 (grub_addr_t
) &(gdt_desc
.limit
));
274 grub_dprintf ("linux", "idt = %x:%x, gdt = %x:%x\n",
275 (unsigned) idt_desc
.limit
, (unsigned) idt_desc
.base
,
276 (unsigned) gdt_desc
.limit
, (unsigned) gdt_desc
.base
);
277 mmap_size
= find_mmap_size ();
278 if (grub_efi_get_memory_map (&mmap_size
, mmap_buf
, &map_key
,
279 &desc_size
, &desc_version
) <= 0)
280 grub_fatal ("cannot get memory map");
282 if (! grub_efi_exit_boot_services (map_key
))
283 grub_fatal ("cannot exit boot services");
285 /* Note that no boot services are available from here. */
287 /* Hardware interrupts are not safe any longer. */
288 asm volatile ("cli" : : );
290 /* Pass EFI parameters. */
291 params
->efi_mem_desc_size
= desc_size
;
292 params
->efi_mem_desc_version
= desc_version
;
293 params
->efi_mmap
= (grub_addr_t
) mmap_buf
;
294 params
->efi_mmap_size
= mmap_size
;
296 /* Pass parameters. */
297 asm volatile ("movl %0, %%esi" : : "m" (real_mode_mem
));
298 asm volatile ("movl %0, %%ecx" : : "m" (lh
->code32_start
));
299 asm volatile ("xorl %%ebx, %%ebx" : : );
301 /* Load the IDT and the GDT for the bootstrap. */
302 asm volatile ("lidt %0" : : "m" (idt_desc
.limit
));
303 asm volatile ("lgdt %0" : : "m" (gdt_desc
.limit
));
306 asm volatile ("jmp *%%ecx" : : );
308 /* Never reach here. */
309 return GRUB_ERR_NONE
;
313 grub_linux_unload (void)
316 grub_dl_unref (my_mod
);
318 return GRUB_ERR_NONE
;
322 grub_rescue_cmd_linux (int argc
, char *argv
[])
324 grub_file_t file
= 0;
325 struct linux_kernel_header lh
;
326 struct linux_kernel_params
*params
;
327 grub_uint8_t setup_sects
;
328 grub_size_t real_size
, prot_size
;
333 grub_dl_ref (my_mod
);
337 grub_error (GRUB_ERR_BAD_ARGUMENT
, "no kernel specified");
341 file
= grub_file_open (argv
[0]);
345 if (grub_file_read (file
, (char *) &lh
, sizeof (lh
)) != sizeof (lh
))
347 grub_error (GRUB_ERR_READ_ERROR
, "cannot read the linux header");
351 if (lh
.boot_flag
!= grub_cpu_to_le16 (0xaa55))
353 grub_error (GRUB_ERR_BAD_OS
, "invalid magic number");
357 if (lh
.setup_sects
> GRUB_LINUX_MAX_SETUP_SECTS
)
359 grub_error (GRUB_ERR_BAD_OS
, "too many setup sectors");
363 /* EFI support is quite new, so reject old versions. */
364 if (lh
.header
!= grub_cpu_to_le32 (GRUB_LINUX_MAGIC_SIGNATURE
)
365 || grub_le_to_cpu16 (lh
.version
) < 0x0203)
367 grub_error (GRUB_ERR_BAD_OS
, "too old version");
371 /* I'm not sure how to support zImage on EFI. */
372 if (! (lh
.loadflags
& GRUB_LINUX_FLAG_BIG_KERNEL
))
374 grub_error (GRUB_ERR_BAD_OS
, "zImage is not supported");
378 setup_sects
= lh
.setup_sects
;
380 /* If SETUP_SECTS is not set, set it to the default (4). */
382 setup_sects
= GRUB_LINUX_DEFAULT_SETUP_SECTS
;
384 real_size
= setup_sects
<< GRUB_DISK_SECTOR_BITS
;
385 prot_size
= grub_file_size (file
) - real_size
- GRUB_DISK_SECTOR_SIZE
;
387 if (! allocate_pages (real_size
, prot_size
))
390 /* XXX Linux assumes that only elilo can boot Linux on EFI!!! */
391 lh
.type_of_loader
= 0x50;
393 lh
.cl_magic
= GRUB_LINUX_CL_MAGIC
;
394 lh
.cl_offset
= GRUB_LINUX_CL_END_OFFSET
;
395 lh
.cmd_line_ptr
= (char *) real_mode_mem
+ GRUB_LINUX_CL_OFFSET
;
396 lh
.ramdisk_image
= 0;
399 params
= (struct linux_kernel_params
*) &lh
;
401 /* These are not needed to be precise, because Linux uses these values
402 only to raise an error when the decompression code cannot find good
404 params
->ext_mem
= ((32 * 0x100000) >> 10);
405 params
->alt_mem
= ((32 * 0x100000) >> 10);
407 params
->video_cursor_x
= grub_efi_system_table
->con_out
->mode
->cursor_column
;
408 params
->video_cursor_y
= grub_efi_system_table
->con_out
->mode
->cursor_row
;
409 params
->video_page
= 0; /* ??? */
410 params
->video_mode
= grub_efi_system_table
->con_out
->mode
->mode
;
411 params
->video_width
= (grub_getwh () >> 8);
412 params
->video_ega_bx
= 0;
413 params
->video_height
= (grub_getwh () & 0xff);
414 params
->have_vga
= 0;
415 params
->font_size
= 16; /* XXX */
418 params
->lfb_width
= 0;
419 params
->lfb_height
= 0;
420 params
->lfb_depth
= 0;
421 params
->lfb_base
= 0;
422 params
->lfb_size
= 0;
423 params
->lfb_line_len
= 0;
424 params
->red_mask_size
= 0;
425 params
->red_field_pos
= 0;
426 params
->green_mask_size
= 0;
427 params
->green_field_pos
= 0;
428 params
->blue_mask_size
= 0;
429 params
->blue_field_pos
= 0;
430 params
->reserved_mask_size
= 0;
431 params
->reserved_field_pos
= 0;
432 params
->vesapm_segment
= 0;
433 params
->vesapm_offset
= 0;
434 params
->lfb_pages
= 0;
435 params
->vesa_attrib
= 0;
438 params
->apm_version
= 0;
439 params
->apm_code_segment
= 0;
440 params
->apm_entry
= 0;
441 params
->apm_16bit_code_segment
= 0;
442 params
->apm_data_segment
= 0;
443 params
->apm_flags
= 0;
444 params
->apm_code_len
= 0;
445 params
->apm_data_len
= 0;
447 /* XXX is there any way to use SpeedStep on EFI? */
448 params
->ist_signature
= 0;
449 params
->ist_command
= 0;
450 params
->ist_event
= 0;
451 params
->ist_perf_level
= 0;
453 /* Let the kernel probe the information. */
454 grub_memset (params
->hd0_drive_info
, 0, sizeof (params
->hd0_drive_info
));
455 grub_memset (params
->hd1_drive_info
, 0, sizeof (params
->hd1_drive_info
));
458 params
->rom_config_len
= 0;
460 params
->efi_signature
= GRUB_LINUX_EFI_SIGNATURE
; /* XXX not used */
461 params
->efi_system_table
= (grub_addr_t
) grub_efi_system_table
;
462 /* The other EFI parameters are filled when booting. */
464 /* No need to fake the BIOS's memory map. */
465 params
->mmap_size
= 0;
467 /* Let the kernel probe the information. */
468 params
->ps_mouse
= 0;
470 /* Clear padding for future compatibility. */
471 grub_memset (params
->padding1
, 0, sizeof (params
->padding1
));
472 grub_memset (params
->padding2
, 0, sizeof (params
->padding2
));
473 grub_memset (params
->padding3
, 0, sizeof (params
->padding3
));
474 grub_memset (params
->padding4
, 0, sizeof (params
->padding4
));
475 grub_memset (params
->padding5
, 0, sizeof (params
->padding5
));
476 grub_memset (params
->padding6
, 0, sizeof (params
->padding6
));
477 grub_memset (params
->padding7
, 0, sizeof (params
->padding7
));
478 grub_memset (params
->padding8
, 0, sizeof (params
->padding8
));
479 grub_memset (params
->padding9
, 0, sizeof (params
->padding9
));
481 /* Put the real mode code at the real location. */
482 grub_memmove (real_mode_mem
, &lh
, sizeof (lh
));
484 len
= real_size
+ GRUB_DISK_SECTOR_SIZE
- sizeof (lh
);
485 if (grub_file_read (file
, (char *) real_mode_mem
+ sizeof (lh
), len
) != len
)
487 grub_error (GRUB_ERR_FILE_READ_ERROR
, "Couldn't read file");
491 /* XXX there is no way to know if the kernel really supports EFI. */
492 grub_printf (" [Linux-EFI, setup=0x%x, size=0x%x]\n",
493 real_size
, prot_size
);
495 /* Detect explicitly specified memory size, if any. */
497 for (i
= 1; i
< argc
; i
++)
498 if (grub_memcmp (argv
[i
], "mem=", 4) == 0)
500 char *val
= argv
[i
] + 4;
502 linux_mem_size
= grub_strtoul (val
, &val
, 0);
506 grub_errno
= GRUB_ERR_NONE
;
513 switch (grub_tolower (val
[0]))
525 /* Check an overflow. */
526 if (linux_mem_size
> (~0UL >> shift
))
529 linux_mem_size
<<= shift
;
533 /* Specify the boot file. */
534 dest
= grub_stpcpy ((char *) real_mode_mem
+ GRUB_LINUX_CL_OFFSET
,
536 dest
= grub_stpcpy (dest
, argv
[0]);
538 /* Copy kernel parameters. */
541 && dest
+ grub_strlen (argv
[i
]) + 1 < ((char *) real_mode_mem
542 + GRUB_LINUX_CL_END_OFFSET
);
546 dest
= grub_stpcpy (dest
, argv
[i
]);
550 if (grub_file_read (file
, (char *) GRUB_LINUX_BZIMAGE_ADDR
, len
) != len
)
551 grub_error (GRUB_ERR_FILE_READ_ERROR
, "Couldn't read file");
553 if (grub_errno
== GRUB_ERR_NONE
)
555 grub_loader_set (grub_linux_boot
, grub_linux_unload
, 1);
562 grub_file_close (file
);
564 if (grub_errno
!= GRUB_ERR_NONE
)
566 grub_dl_unref (my_mod
);
572 grub_rescue_cmd_initrd (int argc
, char *argv
[])
574 grub_file_t file
= 0;
576 grub_addr_t addr_min
, addr_max
;
578 grub_efi_uintn_t mmap_size
;
579 grub_efi_memory_descriptor_t
*desc
;
580 grub_efi_uintn_t desc_size
;
581 struct linux_kernel_header
*lh
;
585 grub_error (GRUB_ERR_BAD_ARGUMENT
, "No module specified");
591 grub_error (GRUB_ERR_BAD_ARGUMENT
, "You need to load the kernel first.");
595 file
= grub_file_open (argv
[0]);
599 size
= grub_file_size (file
);
600 initrd_pages
= (page_align (size
) >> 12);
602 lh
= (struct linux_kernel_header
*) real_mode_mem
;
604 addr_max
= grub_cpu_to_le32 (lh
->initrd_addr_max
);
605 if (linux_mem_size
!= 0 && linux_mem_size
< addr_max
)
606 addr_max
= linux_mem_size
;
608 /* Linux 2.3.xx has a bug in the memory range check, so avoid
610 Linux 2.2.xx has a bug in the memory range check, which is
611 worse than that of Linux 2.3.xx, so avoid the last 64kb. */
614 /* Usually, the compression ratio is about 50%. */
615 addr_min
= (grub_addr_t
) prot_mode_mem
+ ((prot_mode_pages
* 3) << 12);
617 /* Find the highest address to put the initrd. */
618 mmap_size
= find_mmap_size ();
619 if (grub_efi_get_memory_map (&mmap_size
, mmap_buf
, 0, &desc_size
, 0) <= 0)
620 grub_fatal ("cannot get memory map");
623 for (desc
= mmap_buf
;
624 desc
< NEXT_MEMORY_DESCRIPTOR (mmap_buf
, mmap_size
);
625 desc
= NEXT_MEMORY_DESCRIPTOR (desc
, desc_size
))
627 if (desc
->type
== GRUB_EFI_CONVENTIONAL_MEMORY
628 && desc
->physical_start
>= addr_min
629 && desc
->physical_start
+ size
< addr_max
630 && desc
->num_pages
>= initrd_pages
)
632 grub_efi_physical_address_t physical_end
;
634 physical_end
= desc
->physical_start
+ (desc
->num_pages
<< 12);
635 if (physical_end
> addr_max
)
636 physical_end
= addr_max
;
638 if (physical_end
> addr
)
639 addr
= physical_end
- page_align (size
);
645 grub_error (GRUB_ERR_OUT_OF_MEMORY
, "no free pages available");
649 initrd_mem
= grub_efi_allocate_pages (addr
, initrd_pages
);
651 grub_fatal ("cannot allocate pages");
653 if (grub_file_read (file
, initrd_mem
, size
) != size
)
655 grub_error (GRUB_ERR_FILE_READ_ERROR
, "Couldn't read file");
659 grub_printf (" [Initrd, addr=0x%x, size=0x%x]\n",
662 lh
->ramdisk_image
= addr
;
663 lh
->ramdisk_size
= size
;
664 lh
->root_dev
= 0x0100; /* XXX */
668 grub_file_close (file
);
674 grub_rescue_register_command ("linux",
675 grub_rescue_cmd_linux
,
677 grub_rescue_register_command ("initrd",
678 grub_rescue_cmd_initrd
,
685 grub_rescue_unregister_command ("linux");
686 grub_rescue_unregister_command ("initrd");