Merge tag 'upstream/3.14'
[debian/elilo] / x86_64 / system.c
index 6874acc51aa32e2eea7b9d07231a06e916211624..44c2e467a6f3f05d2f33028c11d0c9d1405cc99f 100644 (file)
  */
 #include <efi.h>
 #include <efilib.h>
+#include <string.h>
 
 #include "elilo.h"
 #include "loader.h"
 #include "rmswitch.h"
 
+#define DEBUG_CREATE_BOOT_PARAMS 0
+#if DEBUG_CREATE_BOOT_PARAMS
+#define DPR(a) do { if (elilo_opt.debug) { Print a; } } while ( 0 )
+#else
+#define DPR(a)
+#endif
+
 extern loader_ops_t bzimage_loader, plain_loader, gzip_loader; 
 
 /*
@@ -105,14 +113,16 @@ UINTN high_base_mem = 0x90000;
 UINTN high_ext_mem = 32 * 1024 * 1024;
 
 /* This starting address will hold true for all of the loader types for now */
-VOID *kernel_start = (void *)DEFAULT_KERNEL_START;
+VOID *kernel_start = (VOID *)DEFAULT_KERNEL_START;
 
 /* The kernel may load elsewhere if EFI firmware reserves kernel_start */
-VOID *kernel_load_address = DEFAULT_KERNEL_START; 
+VOID *kernel_load_address = (VOID *)DEFAULT_KERNEL_START;
 
 VOID *initrd_start = NULL;
 UINTN initrd_size = 0;
 
+INTN e820_map_overflow = 0;
+
 INTN
 sysdeps_init(EFI_HANDLE dev)
 {
@@ -131,10 +141,8 @@ sysdeps_init(EFI_HANDLE dev)
 /*
  * initrd_get_addr()
  *     Compute a starting address for the initial RAMdisk image.
- *     For now, this image is placed immediately after the end of
- *     the kernel memory.  Inside the start_kernel() code, the
- *     RAMdisk image will be relocated to the top of available
- *     extended memory.
+ *     For now we suggest 'initrd_addr_max' with room for 32MB,
+ *     as image->pgcnt is not initialized yet.
  */
 INTN
 sysdeps_initrd_get_addr(kdesc_t *kd, memdesc_t *imem)
@@ -146,10 +154,12 @@ sysdeps_initrd_get_addr(kdesc_t *kd, memdesc_t *imem)
                return -1;
        }
 
-       VERB_PRT(3, Print(L"kstart="PTR_FMT"  kentry="PTR_FMT"  kend="PTR_FMT"\n", 
-               kd->kstart, kd->kentry, kd->kend));
+       VERB_PRT(3, Print(L"initrd_addr_max="PTR_FMT" reserve=%d\n",
+               param_start->s.initrd_addr_max, 32*MB));
 
-       imem->start_addr = kd->kend;
+       imem->start_addr = (VOID *)
+               (((UINT64)param_start->s.initrd_addr_max - 32*MB + 1)
+               & ~EFI_PAGE_MASK);
 
        VERB_PRT(3, Print(L"initrd start_addr="PTR_FMT" pgcnt=%d\n", 
                imem->start_addr, imem->pgcnt));
@@ -157,6 +167,48 @@ sysdeps_initrd_get_addr(kdesc_t *kd, memdesc_t *imem)
        return 0;
 }
 
+
+/*
+ * checkfix_initrd()
+ *     Check and possibly fix allocation of initrd memory.
+ */
+VOID *
+sysdeps_checkfix_initrd(VOID *start_addr, memdesc_t *imem)
+{
+       UINTN pgcnt =  EFI_SIZE_TO_PAGES(imem->size);
+       UINT64 initrd_addr_max = (UINT64)param_start->s.initrd_addr_max;
+       UINT64 ki_max = initrd_addr_max - imem->size + 1;
+       VOID *ki_max_addr;
+
+       VERB_PRT( 3, Print(L"loadfile: start_addr="PTR_FMT
+               " ki_max_addr="PTR_FMT"\n", start_addr, (VOID *)ki_max));
+       if (ki_max > UINT32_MAX) {
+               ERR_PRT((L"Force kernel specified initrd_addr_max="PTR_FMT
+                       " below 4GB\n", (VOID *)initrd_addr_max));
+               ki_max = UINT32_MAX - imem->size + 1;
+       }
+       ki_max_addr = (VOID *)ki_max;
+
+       if ((UINT64)start_addr > ki_max) {
+               VERB_PRT(1, Print(L"initrd start_addr="PTR_FMT" above "
+                       "limit="PTR_FMT"\n", start_addr, ki_max_addr));
+               free(start_addr);
+               start_addr = NULL;
+       }
+       /* so either the initial allocation failed or it's been to high! */
+       if (start_addr == NULL) {
+               start_addr = alloc_pages(pgcnt, EfiLoaderData,
+                       AllocateMaxAddress, ki_max_addr);
+       }
+       if ((UINT64)start_addr > ki_max) {
+               ERR_PRT((L"Failed to allocate %d pages below %dMB",
+                       pgcnt, (param_start->s.initrd_addr_max+1)>>20));
+               free(start_addr);
+               start_addr = NULL;
+       }
+       return start_addr;
+}
+
 VOID
 sysdeps_free_boot_params(boot_params_t *bp)
 {
@@ -199,6 +251,10 @@ static INTN get_video_info(boot_params_t * bp) {
         UINTN size1;
         UINT8 i;
 
+       if (x86_64_text_mode() == 1) {
+               Print((L"Skip GOP init, force text-mode.\n"));
+               return -1;
+       }
        efi_status = uefi_call_wrapper(
                        BS->LocateHandle,
                        5,
@@ -209,7 +265,19 @@ static INTN get_video_info(boot_params_t * bp) {
                        (VOID **)Gop_handle);
        
        if (EFI_ERROR(efi_status) && efi_status != EFI_BUFFER_TOO_SMALL) {
-               ERR_PRT((L"LocateHandle GopProtocol failed."));
+               Print(L"LocateHandle GopProtocol failed.\n");
+               Print(L"--Either no graphics head is installed,\n" \
+                      "--efi console is set to serial, or,\n" \
+                      "--the EFI firmware version of this machine is\n" \
+                      "--older than UEFI 2.0. and does not support GOP");
+               Print(L"you can SAFELY IGNORE this error. elilo will\n" \
+                      "default to text-mode.\n Alternatively you can " \
+                      "now force text mode by setting config variable\n" \
+                      "text_mode=1 for x86 in elilo.conf or via cmdline.\n\n");
+               Print(L"However if this is the last text output you see\n" \
+                      "ensure that your kernel console command line\n " \
+                      "variable matches up with the actual efi boot menu\n" \
+                      "console output settings.\n\n");
                return -1;
        }
        Gop_handle = alloc(size, 0);
@@ -326,10 +394,56 @@ static INTN get_video_info(boot_params_t * bp) {
        return 0;
 }
 
+CHAR16 *
+StrStr(IN const CHAR16 *h, IN const CHAR16 *n)
+{
+       const CHAR16 *t = h;
+       CHAR16 *res;
+       int len = 0, i;
+
+       len = StrLen((CHAR16 *)n);
+       while(*t != CHAR_NULL) {
+         res = StrChr( t, n[0]);
+         if (!res) return res;
+         for( i = 1; i < len && res[i] != CHAR_NULL && res[i] == n[i]; i++);
+         if ( i == len ) return res;
+         t = res + 1;
+         if (t > h + CMDLINE_MAXLEN) return (CHAR16 *)0;
+       }
+
+       return (CHAR16 *)0;
+}
+
+CHAR8 *
+StrStr8(IN const CHAR8 *h, IN const CHAR8 *n)
+{
+       const CHAR8 *t = h;
+       CHAR8 *res;
+       int len = 0, i;
+
+       len = strlena((CHAR8 *)n);
+       while(*t != 0) {
+         res = strchra( t, n[0]);
+         if (!res) return res;
+         for( i = 1; i < len && res[i] != 0 && res[i] == n[i]; i++);
+         if ( i == len ) return res;
+         t = res + 1;
+         if (t > (h + CMDLINE_MAXLEN)) return (CHAR8 *)0;
+       }
+
+       return (CHAR8 *)0;
+}
+
 /* Convert EFI memory map to E820 map for the operating system 
  * This code is based on a Linux kernel patch submitted by Edgar Hucek
  */
 
+#if DEBUG_CREATE_BOOT_PARAMS
+static int e820_max = 6;
+#else
+static int e820_max = E820_MAX;
+#endif
+
 /* Add a memory region to the e820 map */
 static void add_memory_region (struct e820entry *e820_map,
                               int *e820_nr_map,
@@ -338,21 +452,56 @@ static void add_memory_region (struct e820entry *e820_map,
                               unsigned int type)
 {
        int x = *e820_nr_map;
+       static unsigned long long estart = 0ULL;
+       static unsigned long esize = 0L;
+       static unsigned int etype = -1;
+       static int merge = 0;
 
-       if (x == E820_MAX) {
-               Print(L"Too many entries in the memory map!\n");
-               return;
-       }
+       if (x == 0)
+               DPR((L"AMR: %3s %4s %16s/%12s/%s\n",
+                       L"idx", L" ", L"start", L"size", L"type"));
 
+       /* merge adjacent regions of same type */
        if ((x > 0) && e820_map[x-1].addr + e820_map[x-1].size == start
-           && e820_map[x-1].type == type)
+           && e820_map[x-1].type == type) {
                e820_map[x-1].size += size;
-       else {
+               estart = e820_map[x-1].addr;
+               esize  = e820_map[x-1].size;
+               etype  = e820_map[x-1].type;
+               merge++;
+               return;
+       }
+       /* fill up to E820_MAX */
+       if ( x < e820_max ) {
                e820_map[x].addr = start;
                e820_map[x].size = size;
                e820_map[x].type = type;
                (*e820_nr_map)++;
+               if (merge) DPR((L"AMR: %3d ==>  %016llx/%012lx/%d (%d)\n",
+                               x-1, estart, esize, etype, merge));
+               merge=0;
+               DPR((L"AMR: %3d add  %016llx/%012lx/%d\n",
+                       x, start, size, type));
+               return;
        }
+       /* different type means another region didn't fit */
+       /* or same type, but there's a hole */
+       if (etype != type || (estart + esize) != start) {
+               if (merge) DPR((L"AMR: %3d ===> %016llx/%012lx/%d (%d)\n",
+                       e820_map_overflow, estart, esize, etype, merge));
+               merge = 0;
+               estart = start;
+               esize = size;
+               etype = type;
+               e820_map_overflow++;
+               DPR((L"AMR: %3d OVER %016llx/%012lx/%d\n",
+                        e820_map_overflow, start, size, type));
+               return;
+       }
+       /* same type and no hole, merge it */
+       estart += esize;
+       esize += size;
+       merge++;
 }
 
 void fill_e820map(boot_params_t *bp, mmap_desc_t *mdesc)
@@ -431,6 +580,7 @@ void fill_e820map(boot_params_t *bp, mmap_desc_t *mdesc)
                        break;
                default:
                        /* We should not hit this case */
+                       DBG_PRT((L"hit default!?"));
                        add_memory_region(e820_map, &e820_nr_map,
                                          md->PhysicalStart,
                                          md->NumberOfPages << EFI_PAGE_SHIFT,
@@ -444,6 +594,8 @@ void fill_e820map(boot_params_t *bp, mmap_desc_t *mdesc)
 
 /*
  * x86_64 specific boot parameters initialization routine
+ *
+ * Note: debug and verbose messages have already been turned off!
  */
 INTN
 sysdeps_create_boot_params(
@@ -459,6 +611,12 @@ sysdeps_create_boot_params(
        UINT8 row, col;
        UINT8 mode;
        UINT16 hdr_version;
+       UINT8 e820_map_overflow_warned = 0;
+
+#if DEBUG_CREATE_BOOT_PARAMS
+       elilo_opt.debug=1;
+       elilo_opt.verbose=5;
+#endif
 
        DBG_PRT((L"fill_boot_params()\n"));
 
@@ -493,17 +651,9 @@ sysdeps_create_boot_params(
        hdr_version = (bp->s.hdr_major << 8) | bp->s.hdr_minor;
 
        /*
-        * Clear out unused memory in boot sector image.
+        * Do NOT clear out unknown memory in boot sector image.
+        * This breaks boot protocol >= 2.10 (2.6.31).
         */
-       bp->s.unused_1 = 0;
-       bp->s.unused_2 = 0;
-       ZeroMem(&bp->s.unused_3, sizeof bp->s.unused_3);
-       ZeroMem(&bp->s.unused_4, sizeof bp->s.unused_4);
-       ZeroMem(&bp->s.unused_51, sizeof bp->s.unused_51);
-       ZeroMem(&bp->s.unused_52, sizeof bp->s.unused_52);
-       bp->s.unused_6 = 0;
-       bp->s.unused_7 = 0;
-       ZeroMem(bp->s.unused_8, sizeof bp->s.unused_8);
 
        /*
         * Tell kernel this was loaded by an advanced loader type.
@@ -553,19 +703,19 @@ sysdeps_create_boot_params(
        DBG_PRT((L"initrd->start_addr="PTR_FMT"  initrd->pgcnt=%d\n",
                initrd->start_addr, initrd->pgcnt));
 
-       /* These RAMdisk flags are not needed, just zero them. */
-       bp->s.ramdisk_flags = 0;
+       /* These RAMdisk flags are not needed, just zero them. NOT!*/
+       /* 'ramdisk_flags' (@0x1F8) is called 'ram_size' in the meantime, */
+       /* see Documentation/x86/boot.txt. */
 
        if (initrd->start_addr && initrd->pgcnt) {
+               if ( (UINT64)initrd->start_addr > UINT32_MAX ) {
+                       ERR_PRT((L"Start of initrd out of reach (>4GB)."));
+                       free_kmem();
+                       return -1;
+               }
                /* %%TBD - This will probably have to be changed. */
                bp->s.initrd_start = (UINT32)(UINT64)initrd->start_addr;
                bp->s.initrd_size = (UINT32)(initrd->size);
-               /*
-                * This is the RAMdisk root device for RedHat 2.2.x
-                * kernels (major 0x01, minor 0x00).
-                */
-
-               bp->s.orig_root_dev = 0x0100;
        } else {
                bp->s.initrd_start = 0;
                bp->s.initrd_size = 0;
@@ -589,11 +739,6 @@ sysdeps_create_boot_params(
        bp->s.mca_info_len = 0;
        ZeroMem(bp->s.mca_info_buf, sizeof bp->s.mca_info_buf);
 
-       /*
-        * Pointing device presence.  The kernel will detect this.
-        */
-       bp->s.aux_dev_info = NO_MOUSE;
-
        /*
         * EFI loader signature 
         */
@@ -602,6 +747,11 @@ sysdeps_create_boot_params(
        /*
         * Kernel entry point.
         */
+       if ( (UINT64)kernel_start != (UINT32)(UINT64)kernel_start ) {
+               ERR_PRT((L"Start of kernel (will be) out of reach (>4GB)."));
+               free_kmem();
+               return -1;
+       }
        bp->s.kernel_start = (UINT32)(UINT64)kernel_start;
 
        /*
@@ -692,11 +842,9 @@ sysdeps_create_boot_params(
                CHECK_OFFSET(setup_sectors, 0x1F1, L"%xh");
                CHECK_OFFSET(mount_root_rdonly, 0x1F2, L"%xh");
                CHECK_OFFSET(sys_size, 0x1F4, L"%xh");
-               CHECK_OFFSET(swap_dev, 0x1F6, L"%xh");
-               CHECK_OFFSET(ramdisk_flags, 0x1F8, L"%xh");
                CHECK_OFFSET(video_mode_flag, 0x1FA, L"%xh");
                CHECK_OFFSET(orig_root_dev, 0x1FC, L"%xh");
-               CHECK_OFFSET(aux_dev_info, 0x1FF, L"%xh");
+               CHECK_OFFSET(boot_flag, 0x1FE, L"%xh");
                CHECK_OFFSET(jump, 0x200, L"%xh");
                CHECK_OFFSET(setup_sig, 0x202, L"'%-4.4a'");
                CHECK_OFFSET(hdr_minor, 0x206, L"%xh");
@@ -710,9 +858,9 @@ sysdeps_create_boot_params(
                CHECK_OFFSET(kernel_start, 0x214, L"%xh");
                CHECK_OFFSET(initrd_start, 0x218, L"%xh");
                CHECK_OFFSET(initrd_size, 0x21C, L"%xh");
-               CHECK_OFFSET(bootsect_helper, 0x220, L"%xh");
                CHECK_OFFSET(heap_end_ptr, 0x224, L"%xh");
                CHECK_OFFSET(cmdline_addr, 0x228, L"%xh");
+               CHECK_OFFSET(e820_map, 0x2D0, L"%xh");
 
                if (test) {
                        ERR_PRT((L"Boot sector and/or setup parameter alignment error."));
@@ -802,6 +950,31 @@ do_memmap:
         * and update the bootparam accordingly
         */
        fill_e820map(bp, &mdesc);
+
+#if DEBUG_CREATE_BOOT_PARAMS
+       if ( e820_map_overflow == 0 )
+               e820_map_overflow = -1; /* force second get_memmap()! */
+#endif
+       if (e820_map_overflow && !e820_map_overflow_warned) {
+               CHAR8 *aem = (CHAR8 *)"add_efi_memmap";
+               e820_map_overflow_warned++;
+
+#if DEBUG_CREATE_BOOT_PARAMS
+               elilo_opt.debug=0;
+               elilo_opt.verbose=0;
+#endif
+               if (e820_map_overflow == -1 || StrStr8(cmdline, aem)) {
+                       /* Print(L"...mapping again, silently!\n"); */
+                       goto do_memmap;
+               }
+
+               Print(L"\nCAUTION: EFI memory map has %d more entr%a"
+                       " than E820 map supports.\n"
+                       "To access all memory, '%a' may be necessary.\n\n",
+                       e820_map_overflow, (e820_map_overflow==1)?"y":"ies",
+                       aem);
+               goto do_memmap;
+       }
        
        return 0;
 }