From 51955d52d8ca6f65fb10097951ed678be8f61b13 Mon Sep 17 00:00:00 2001 From: okuji Date: Fri, 12 Jul 2002 09:55:55 +0000 Subject: [PATCH] 2002-07-12 Yoshinori K. Okuji * stage2/boot.c (load_image): Rewrite the Linux booting support radically. Now it should work even on a machine having, say, only 128KB, theoretically. Of course, GRUB itself doesn't work on such a system, though. (load_initrd): Initialize LH based on CUR_ADDR, because the location becomes dynamic. * stage2/shared.h (LINUX_MAX_SETUP_SECTS): Set to 64. (LINUX_HEAP_END_OFFSET): Set to (0x9000 - 0x200). (LINUX_STAGING_AREA): Removed. (LINUX_SETUP): Likewise. (LINUX_KERNEL): Likewise. (LINUX_KERNEL_MAXLEN): Likewise. (LINUX_SETUP_SEG): Likewise. (LINUX_INIT_SEG): Likewise. (LINUX_SETUP_STACK): Set to 0x9000. (LINUX_BZIMAGE_ADDR): New macro. (LINUX_ZIMAGE_ADDR): Likewise. (LINUX_OLD_REAL_MODE_ADDR): Likewise. (CL_MY_LOCATION): Removed. (CL_MY_END_ADDR): Likewise. (CL_BASE_ADDR): Likewise. (CL_MAGIC): Renamed to ... (LINUX_CL_MAGIC): ... this. (LINUX_CL_OFFSET): New macro. (LINUX_CL_END_OFFSET): Likewise. (LINUX_SETUP_MOVE_SIZE): Likewise. (struct linux_kernel_header): Change the type of the member "cmd_line_ptr" to char *. (linux_data_tmp_addr): Declared. (linux_data_real_addr): Likewise. * stage2/asm.S [!STAGE1_5] (linux_data_tmp_addr): New variable. [!STAGE1_5] (linux_data_real_addr): Likewise. [!STAGE1_5] (big_linux_boot): Copy the real mode part from LINUX_DATA_TMP_ADDR to LINUX_DATA_REAL_ADDR. * grub/asmstub.c (linux_data_tmp_addr): New variable. (linux_data_real_addr): Likewise. --- ChangeLog | 39 +++++++++++++++ NEWS | 2 + grub/asmstub.c | 2 + stage2/asm.S | 40 +++++++++++---- stage2/boot.c | 126 +++++++++++++++++++++++++----------------------- stage2/shared.h | 37 +++++++------- 6 files changed, 158 insertions(+), 88 deletions(-) diff --git a/ChangeLog b/ChangeLog index 52dab6484..f1bf0393e 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,42 @@ +2002-07-12 Yoshinori K. Okuji + + * stage2/boot.c (load_image): Rewrite the Linux booting support + radically. Now it should work even on a machine having, say, + only 128KB, theoretically. Of course, GRUB itself doesn't work + on such a system, though. + (load_initrd): Initialize LH based on CUR_ADDR, because the + location becomes dynamic. + * stage2/shared.h (LINUX_MAX_SETUP_SECTS): Set to 64. + (LINUX_HEAP_END_OFFSET): Set to (0x9000 - 0x200). + (LINUX_STAGING_AREA): Removed. + (LINUX_SETUP): Likewise. + (LINUX_KERNEL): Likewise. + (LINUX_KERNEL_MAXLEN): Likewise. + (LINUX_SETUP_SEG): Likewise. + (LINUX_INIT_SEG): Likewise. + (LINUX_SETUP_STACK): Set to 0x9000. + (LINUX_BZIMAGE_ADDR): New macro. + (LINUX_ZIMAGE_ADDR): Likewise. + (LINUX_OLD_REAL_MODE_ADDR): Likewise. + (CL_MY_LOCATION): Removed. + (CL_MY_END_ADDR): Likewise. + (CL_BASE_ADDR): Likewise. + (CL_MAGIC): Renamed to ... + (LINUX_CL_MAGIC): ... this. + (LINUX_CL_OFFSET): New macro. + (LINUX_CL_END_OFFSET): Likewise. + (LINUX_SETUP_MOVE_SIZE): Likewise. + (struct linux_kernel_header): Change the type of the member + "cmd_line_ptr" to char *. + (linux_data_tmp_addr): Declared. + (linux_data_real_addr): Likewise. + * stage2/asm.S [!STAGE1_5] (linux_data_tmp_addr): New variable. + [!STAGE1_5] (linux_data_real_addr): Likewise. + [!STAGE1_5] (big_linux_boot): Copy the real mode part from + LINUX_DATA_TMP_ADDR to LINUX_DATA_REAL_ADDR. + * grub/asmstub.c (linux_data_tmp_addr): New variable. + (linux_data_real_addr): Likewise. + 2002-07-09 Yoshinori K. Okuji From Mark Kettenis : diff --git a/NEWS b/NEWS index f1633eccd..53ed4b6a8 100644 --- a/NEWS +++ b/NEWS @@ -20,6 +20,8 @@ New in 0.93: intelligent terminal (such as the comint mode in GNU Emacs). * The utility ``grub-md5-crypt'' prompts to retype a password and checks if the passwords match. +* Support for booting Linux is rewritten, so GRUB now supports + large-EBDA systems. New in 0.92 - 2002-04-30: * The command "displaymem" uses only hex digits for consistency. diff --git a/grub/asmstub.c b/grub/asmstub.c index ba6b0c4ef..4de3a3719 100644 --- a/grub/asmstub.c +++ b/grub/asmstub.c @@ -73,6 +73,8 @@ int saved_entryno = 0; char version_string[] = VERSION; char config_file[128] = "/boot/grub/menu.lst"; /* FIXME: arbitrary */ unsigned long linux_text_len = 0; +char *linux_data_tmp_addr = 0; +char *linux_data_real_addr = 0; unsigned short io_map[IO_MAP_SIZE]; struct apm_info apm_bios_info; diff --git a/stage2/asm.S b/stage2/asm.S index d2e84aa54..cc1687203 100644 --- a/stage2/asm.S +++ b/stage2/asm.S @@ -1728,6 +1728,12 @@ ENTRY(patch_code_end) VARIABLE(linux_text_len) .long 0 +VARIABLE(linux_data_tmp_addr) + .long 0 + +VARIABLE(linux_data_real_addr) + .long 0 + ENTRY(linux_boot) /* don't worry about saving anything, we're committed at this point */ cld /* forward copying */ @@ -1736,13 +1742,29 @@ ENTRY(linux_boot) movl EXT_C(linux_text_len), %ecx addl $3, %ecx shrl $2, %ecx - movl $LINUX_STAGING_AREA, %esi - movl $LINUX_KERNEL, %edi + movl $LINUX_BZIMAGE_ADDR, %esi + movl $LINUX_ZIMAGE_ADDR, %edi rep movsl ENTRY(big_linux_boot) + movl EXT_C(linux_data_real_addr), %ebx + + /* copy the real mode part */ + movl EXT_C(linux_data_tmp_addr), %esi + movl %ebx, %edi + movl $LINUX_SETUP_MOVE_SIZE, %ecx + cld + rep + movsb + + /* change %ebx to the segment address */ + shrl $4, %ebx + movl %ebx, %eax + addl $0x20, %eax + movl %eax, linux_setup_seg + /* XXX new stack pointer in safe area for calling functions */ movl $0x4000, %esp call EXT_C(stop_floppy) @@ -1754,20 +1776,20 @@ ENTRY(big_linux_boot) /* final setup for linux boot */ cli - movw $LINUX_INIT_SEG, %ax - movw %ax, %ss + movw %bx, %ss movw $LINUX_SETUP_STACK, %sp - movw %ax, %ds - movw %ax, %es - movw %ax, %fs - movw %ax, %gs + movw %bx, %ds + movw %bx, %es + movw %bx, %fs + movw %bx, %gs /* jump to start */ /* ljmp */ .byte 0xea .word 0 - .word LINUX_SETUP_SEG +linux_setup_seg: + .word 0 .code32 diff --git a/stage2/boot.c b/stage2/boot.c index 3e39099e8..f490d1593 100644 --- a/stage2/boot.c +++ b/stage2/boot.c @@ -227,6 +227,13 @@ load_image (char *kernel, char *arg, kernel_t suggested_type, big_linux = (lh->loadflags & LINUX_FLAG_BIG_KERNEL); lh->type_of_loader = LINUX_BOOT_LOADER_TYPE; + /* Put the real mode part at as a high location as possible. */ + linux_data_real_addr + = (char *) ((mbi.mem_lower << 10) - LINUX_SETUP_MOVE_SIZE); + /* But it must not exceed the traditional area. */ + if (linux_data_real_addr > (char *) LINUX_OLD_REAL_MODE_ADDR) + linux_data_real_addr = (char *) LINUX_OLD_REAL_MODE_ADDR; + if (lh->version >= 0x0201) { lh->heap_end_ptr = LINUX_HEAP_END_OFFSET; @@ -234,22 +241,23 @@ load_image (char *kernel, char *arg, kernel_t suggested_type, } if (lh->version >= 0x0202) - lh->cmd_line_ptr = CL_MY_LOCATION; + lh->cmd_line_ptr = linux_data_real_addr + LINUX_CL_OFFSET; else { - lh->cl_magic = CL_MAGIC; - lh->cl_offset = CL_MY_LOCATION - CL_BASE_ADDR; - lh->setup_move_size - = (unsigned short) (CL_MY_END_ADDR - CL_BASE_ADDR + 1); + lh->cl_magic = LINUX_CL_MAGIC; + lh->cl_offset = LINUX_CL_OFFSET; + lh->setup_move_size = LINUX_SETUP_MOVE_SIZE; } } else { /* Your kernel is quite old... */ - lh->cl_magic = CL_MAGIC; - lh->cl_offset = CL_MY_LOCATION - CL_BASE_ADDR; + lh->cl_magic = LINUX_CL_MAGIC; + lh->cl_offset = LINUX_CL_OFFSET; setup_sects = LINUX_DEFAULT_SETUP_SECTS; + + linux_data_real_addr = (char *) LINUX_OLD_REAL_MODE_ADDR; } /* If SETUP_SECTS is not set, set it to the default (4). */ @@ -258,22 +266,23 @@ load_image (char *kernel, char *arg, kernel_t suggested_type, data_len = setup_sects << 9; text_len = filemax - data_len - SECTOR_SIZE; + + linux_data_tmp_addr = (char *) LINUX_BZIMAGE_ADDR + text_len; - if (! big_linux && text_len > LINUX_KERNEL_MAXLEN) + if (! big_linux + && text_len > linux_data_real_addr - (char *) LINUX_ZIMAGE_ADDR) { grub_printf (" linux 'zImage' kernel too big, try 'make bzImage'\n"); - grub_close (); errnum = ERR_WONT_FIT; - return KERNEL_TYPE_NONE; } - - grub_printf (" [Linux-%s, setup=0x%x, size=0x%x]\n", - (big_linux ? "bzImage" : "zImage"), data_len, text_len); - - /* FIXME: SETUP_SECTS should be supported up to 63. - But do you know there are >640KB conventional memory machines? */ - if (mbi.mem_lower >= 608 && setup_sects < 60) + else if (linux_data_real_addr + LINUX_SETUP_MOVE_SIZE + > RAW_ADDR ((char *) (mbi.mem_lower << 10))) + errnum = ERR_WONT_FIT; + else { + grub_printf (" [Linux-%s, setup=0x%x, size=0x%x]\n", + (big_linux ? "bzImage" : "zImage"), data_len, text_len); + /* Video mode selection support. What a mess! */ /* NOTE: Even the word "mess" is not still enough to represent how wrong and bad the Linux video support is, @@ -281,14 +290,14 @@ load_image (char *kernel, char *arg, kernel_t suggested_type, any more. -okuji */ { char *vga; - + /* Find the substring "vga=". */ vga = grub_strstr (arg, "vga="); if (vga) { char *value = vga + 4; int vid_mode; - + /* Handle special strings. */ if (substring ("normal", value) < 1) vid_mode = LINUX_VID_MODE_NORMAL; @@ -305,7 +314,7 @@ load_image (char *kernel, char *arg, kernel_t suggested_type, grub_close (); return KERNEL_TYPE_NONE; } - + lh->vid_mode = vid_mode; } } @@ -313,12 +322,12 @@ load_image (char *kernel, char *arg, kernel_t suggested_type, /* Check the mem= option to limit memory used for initrd. */ { char *mem; - + mem = grub_strstr (arg, "mem="); if (mem) { char *value = mem + 4; - + safe_parse_maxint (&value, &linux_mem_size); switch (errnum) { @@ -329,11 +338,11 @@ load_image (char *kernel, char *arg, kernel_t suggested_type, linux_mem_size = LINUX_INITRD_MAX_ADDRESS; errnum = ERR_NONE; break; - + case ERR_NONE: { int shift = 0; - + switch (grub_tolower (*value)) { case 'g': @@ -345,7 +354,7 @@ load_image (char *kernel, char *arg, kernel_t suggested_type, default: break; } - + /* Check an overflow. */ if (linux_mem_size > (MAXINT >> shift)) linux_mem_size = LINUX_INITRD_MAX_ADDRESS; @@ -353,7 +362,7 @@ load_image (char *kernel, char *arg, kernel_t suggested_type, linux_mem_size <<= shift; } break; - + default: linux_mem_size = 0; errnum = ERR_NONE; @@ -363,26 +372,26 @@ load_image (char *kernel, char *arg, kernel_t suggested_type, else linux_mem_size = 0; } - + /* It is possible that DATA_LEN is greater than MULTIBOOT_SEARCH, so the data may have been read partially. */ if (data_len <= MULTIBOOT_SEARCH) - grub_memmove ((char *) LINUX_SETUP, buffer, + grub_memmove (linux_data_tmp_addr, buffer, data_len + SECTOR_SIZE); else { - grub_memmove ((char *) LINUX_SETUP, buffer, MULTIBOOT_SEARCH); - grub_read ((char *) LINUX_SETUP + MULTIBOOT_SEARCH, + grub_memmove (linux_data_tmp_addr, buffer, MULTIBOOT_SEARCH); + grub_read (linux_data_tmp_addr + MULTIBOOT_SEARCH, data_len + SECTOR_SIZE - MULTIBOOT_SEARCH); } - + if (lh->header != LINUX_MAGIC_SIGNATURE || lh->version < 0x0200) /* Clear the heap space. */ - grub_memset ((char *) LINUX_SETUP + ((setup_sects - 1) << 9), + grub_memset (linux_data_tmp_addr + ((setup_sects + 1) << 9), 0, (64 - setup_sects - 1) << 9); - + /* Copy command-line plus memory hack to staging area. NOTE: Linux has a bug that it doesn't handle multiple spaces between two options and a space after a "mem=" option isn't @@ -393,39 +402,40 @@ load_image (char *kernel, char *arg, kernel_t suggested_type, avoid to copy spaces unnecessarily. Hell. */ { char *src = skip_to (0, arg); - char *dest = (char *) CL_MY_LOCATION; - - while (((int) dest) < CL_MY_END_ADDR && *src) + char *dest = linux_data_tmp_addr + LINUX_CL_OFFSET; + + while (dest < linux_data_tmp_addr + LINUX_CL_END_OFFSET && *src) *(dest++) = *(src++); - + /* Add a mem option automatically only if the user doesn't specify it explicitly. */ if (! grub_strstr (arg, "mem=") - && ! (load_flags & KERNEL_LOAD_NO_MEM_OPTION)) + && ! (load_flags & KERNEL_LOAD_NO_MEM_OPTION) + && dest + 15 < linux_data_tmp_addr + LINUX_CL_END_OFFSET) { - if (dest != (char *) CL_MY_LOCATION) - *(dest++) = ' '; - - grub_memmove (dest, "mem=", 4); - dest += 4; - + *dest++ = ' '; + *dest++ = 'm'; + *dest++ = 'e'; + *dest++ = 'm'; + *dest++ = '='; + dest = convert_to_ascii (dest, 'u', (extended_memory + 0x400)); - *(dest++) = 'K'; + *dest++ = 'K'; } - + *dest = 0; } - + /* offset into file */ grub_seek (data_len + SECTOR_SIZE); - - cur_addr = LINUX_STAGING_AREA + text_len; - grub_read ((char *) LINUX_STAGING_AREA, text_len); - + + cur_addr = (int) linux_data_tmp_addr + LINUX_SETUP_MOVE_SIZE; + grub_read ((char *) LINUX_BZIMAGE_ADDR, text_len); + if (errnum == ERR_NONE) { grub_close (); - + /* Sanity check. */ if (suggested_type != KERNEL_TYPE_NONE && ((big_linux && suggested_type != KERNEL_TYPE_BIG_LINUX) @@ -434,17 +444,13 @@ load_image (char *kernel, char *arg, kernel_t suggested_type, errnum = ERR_EXEC_FORMAT; return KERNEL_TYPE_NONE; } - + /* Ugly hack. */ linux_text_len = text_len; - + return big_linux ? KERNEL_TYPE_BIG_LINUX : KERNEL_TYPE_LINUX; } - - grub_close (); } - else - errnum = ERR_WONT_FIT; } else /* no recognizable format */ errnum = ERR_EXEC_FORMAT; @@ -780,7 +786,8 @@ load_initrd (char *initrd) { int len; unsigned long moveto; - struct linux_kernel_header *lh; + struct linux_kernel_header *lh + = (struct linux_kernel_header *) (cur_addr - LINUX_SETUP_MOVE_SIZE); #ifndef NO_DECOMPRESSION no_decompression = 1; @@ -815,7 +822,6 @@ load_initrd (char *initrd) printf (" [Linux-initrd @ 0x%x, 0x%x bytes]\n", moveto, len); /* FIXME: Should check if the kernel supports INITRD. */ - lh = (struct linux_kernel_header *) LINUX_SETUP; lh->ramdisk_image = RAW_ADDR (moveto); lh->ramdisk_size = len; diff --git a/stage2/shared.h b/stage2/shared.h index cf51549e2..97e6a9abe 100644 --- a/stage2/shared.h +++ b/stage2/shared.h @@ -138,29 +138,26 @@ extern char *grub_scratch_mem; #define LINUX_DEFAULT_SETUP_SECTS 4 #define LINUX_FLAG_CAN_USE_HEAP 0x80 #define LINUX_INITRD_MAX_ADDRESS 0x38000000 -#define LINUX_MAX_SETUP_SECTS 63 +#define LINUX_MAX_SETUP_SECTS 64 #define LINUX_BOOT_LOADER_TYPE 0x71 -#define LINUX_HEAP_END_OFFSET (0x7F00 - 0x200) +#define LINUX_HEAP_END_OFFSET (0x9000 - 0x200) -#define LINUX_STAGING_AREA RAW_ADDR (0x100000) -#define LINUX_SETUP RAW_ADDR (0x90000) -#define LINUX_KERNEL RAW_ADDR (0x10000) -#define LINUX_KERNEL_MAXLEN 0x7F000 -#define LINUX_SETUP_SEG 0x9020 -#define LINUX_INIT_SEG 0x9000 -#define LINUX_SETUP_STACK 0x7F00 +#define LINUX_BZIMAGE_ADDR RAW_ADDR (0x100000) +#define LINUX_ZIMAGE_ADDR RAW_ADDR (0x10000) +#define LINUX_OLD_REAL_MODE_ADDR RAW_ADDR (0x90000) +#define LINUX_SETUP_STACK 0x9000 -#define LINUX_FLAG_BIG_KERNEL 0x1 +#define LINUX_FLAG_BIG_KERNEL 0x1 /* Linux's video mode selection support. Actually I hate it! */ -#define LINUX_VID_MODE_NORMAL 0xFFFF -#define LINUX_VID_MODE_EXTENDED 0xFFFE -#define LINUX_VID_MODE_ASK 0xFFFD +#define LINUX_VID_MODE_NORMAL 0xFFFF +#define LINUX_VID_MODE_EXTENDED 0xFFFE +#define LINUX_VID_MODE_ASK 0xFFFD -#define CL_MY_LOCATION RAW_ADDR (0x97F00) -#define CL_MY_END_ADDR RAW_ADDR (0x97FFF) -#define CL_MAGIC 0xA33F -#define CL_BASE_ADDR RAW_ADDR (0x90000) +#define LINUX_CL_OFFSET 0x9000 +#define LINUX_CL_END_OFFSET 0x90FF +#define LINUX_SETUP_MOVE_SIZE 0x9100 +#define LINUX_CL_MAGIC 0xA33F /* * General disk stuff @@ -376,7 +373,7 @@ extern char *grub_scratch_mem; #include "mb_header.h" #include "mb_info.h" -/* For the Linux/i386 boot protocol version 2.02. */ +/* For the Linux/i386 boot protocol version 2.03. */ struct linux_kernel_header { char code1[0x0020]; @@ -405,7 +402,7 @@ struct linux_kernel_header unsigned long bootsect_kludge; /* obsolete */ unsigned short heap_end_ptr; /* Free memory after setup end */ unsigned short pad1; /* Unused */ - unsigned long cmd_line_ptr; /* Points to the kernel command line */ + char *cmd_line_ptr; /* Points to the kernel command line */ } __attribute__ ((packed)); /* Memory map address range descriptor used by GET_MMAP_ENTRY. */ @@ -550,6 +547,8 @@ extern unsigned char force_lba; extern char version_string[]; extern char config_file[]; extern unsigned long linux_text_len; +extern char *linux_data_tmp_addr; +extern char *linux_data_real_addr; #ifdef GRUB_UTIL /* If not using config file, this variable is set to zero,