text: /** @file
  
    Copyright (c) 2011 - 2014, Intel Corporation. All rights reserved.<BR>
  
    This program and the accompanying materials
    are licensed and made available under the terms and conditions of the BSD License
    which accompanies this distribution.  The full text of the license may be found at
    http://opensource.org/licenses/bsd-license.php
  
    THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
    WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
  
  **/
  
  #include "LoadLinuxLib.h"
  
  
  /**
    A simple check of the kernel setup image
  
    An assumption is made that the size of the data is at least the
    size of struct boot_params.
  
    @param[in]    KernelSetup - The kernel setup image
  
    @retval    EFI_SUCCESS - The kernel setup looks valid and supported
    @retval    EFI_INVALID_PARAMETER - KernelSetup was NULL
    @retval    EFI_UNSUPPORTED - The kernel setup is not valid or supported
  
  **/
  STATIC
  EFI_STATUS
  EFIAPI
  BasicKernelSetupCheck (
    IN VOID        *KernelSetup
    )
  {
    return LoadLinuxCheckKernelSetup(KernelSetup, sizeof (struct boot_params));
  }
  
  
  EFI_STATUS
  EFIAPI
  LoadLinuxCheckKernelSetup (
    IN VOID        *KernelSetup,
    IN UINTN       KernelSetupSize
    )
  {
    struct boot_params        *Bp;
  
    if (KernelSetup == NULL) {
      return EFI_INVALID_PARAMETER;
    }
  
    if (KernelSetupSize < sizeof (*Bp)) {
      return EFI_UNSUPPORTED;
    }
  
    Bp = (struct boot_params*) KernelSetup;
  
    if ((Bp->hdr.signature != 0xAA55) || // Check boot sector signature
        (Bp->hdr.header != SETUP_HDR) ||
        (Bp->hdr.version < 0x205) || // We only support relocatable kernels
        (!Bp->hdr.relocatable_kernel)
       ) {
      return EFI_UNSUPPORTED;
    } else {
      return EFI_SUCCESS;
    }
  }
  
  
  UINTN
  EFIAPI
  LoadLinuxGetKernelSize (
    IN VOID        *KernelSetup,
    IN UINTN       KernelSize
    )
  {
    struct boot_params        *Bp;
  
    if (EFI_ERROR (BasicKernelSetupCheck (KernelSetup))) {
      return 0;
    }
  
    Bp = (struct boot_params*) KernelSetup;
  
    if (Bp->hdr.version > 0x20a) {
      return Bp->hdr.init_size;
    } else {
      //
      // Add extra size for kernel decompression
      //
      return 3 * KernelSize;
    }
  }
  
  
  VOID*
  EFIAPI
  LoadLinuxAllocateKernelSetupPages (
    IN UINTN                  Pages
    )
  {
    EFI_STATUS                Status;
    EFI_PHYSICAL_ADDRESS      Address;
  
    Address = BASE_1GB;
    Status = gBS->AllocatePages (
                    AllocateMaxAddress,
                    EfiLoaderData,
                    Pages,
                    &Address
                    );
    if (!EFI_ERROR (Status)) {
      return (VOID*)(UINTN) Address;
    } else {
      return NULL;
    }
  }
  
  EFI_STATUS
  EFIAPI
  LoadLinuxInitializeKernelSetup (
    IN VOID        *KernelSetup
    )
  {
    EFI_STATUS                Status;
    UINTN                     SetupEnd;
    struct boot_params        *Bp;
  
    Status = BasicKernelSetupCheck (KernelSetup);
    if (EFI_ERROR (Status)) {
      return Status;
    }
  
    Bp = (struct boot_params*) KernelSetup;
  
    SetupEnd = 0x202 + (Bp->hdr.jump & 0xff);
  
    //
    // Clear all but the setup_header
    //
    ZeroMem (KernelSetup, 0x1f1);
    ZeroMem (((UINT8 *)KernelSetup) + SetupEnd, 4096 - SetupEnd);
    DEBUG ((EFI_D_INFO, "Cleared kernel setup 0-0x1f1, 0x%x-0x1000\n", SetupEnd));
  
    return EFI_SUCCESS;
  }
  
  VOID*
  EFIAPI
  LoadLinuxAllocateKernelPages (
    IN VOID                   *KernelSetup,
    IN UINTN                  Pages
    )
  {
    EFI_STATUS                Status;
    EFI_PHYSICAL_ADDRESS      KernelAddress;
    UINT32                    Loop;
    struct boot_params        *Bp;
  
    if (EFI_ERROR (BasicKernelSetupCheck (KernelSetup))) {
      return NULL;
    }
  
    Bp = (struct boot_params*) KernelSetup;
  
    for (Loop = 1; Loop < 512; Loop++) {
      KernelAddress = MultU64x32 (
                        2 * Bp->hdr.kernel_alignment,
                        Loop
                        );
      Status = gBS->AllocatePages (
                      AllocateAddress,
                      EfiLoaderData,
                      Pages,
                      &KernelAddress
                      );
      if (!EFI_ERROR (Status)) {
        return (VOID*)(UINTN) KernelAddress;
      }
    }
  
    return NULL;
  }
  
  
  VOID*
  EFIAPI
  LoadLinuxAllocateCommandLinePages (
    IN UINTN                  Pages
    )
  {
    EFI_STATUS                Status;
    EFI_PHYSICAL_ADDRESS      Address;
  
    Address = 0xa0000;
    Status = gBS->AllocatePages (
                    AllocateMaxAddress,
                    EfiLoaderData,
                    Pages,
                    &Address
                    );
    if (!EFI_ERROR (Status)) {
      return (VOID*)(UINTN) Address;
    } else {
      return NULL;
    }
  }
  
  
  VOID*
  EFIAPI
  LoadLinuxAllocateInitrdPages (
    IN VOID                   *KernelSetup,
    IN UINTN                  Pages
    )
  {
    EFI_STATUS                Status;
    EFI_PHYSICAL_ADDRESS      Address;
  
    struct boot_params        *Bp;
  
    if (EFI_ERROR (BasicKernelSetupCheck (KernelSetup))) {
      return NULL;
    }
  
    Bp = (struct boot_params*) KernelSetup;
  
    Address = (EFI_PHYSICAL_ADDRESS)(UINTN) Bp->hdr.ramdisk_max;
    Status = gBS->AllocatePages (
                    AllocateMaxAddress,
                    EfiLoaderData,
                    Pages,
                    &Address
                    );
    if (!EFI_ERROR (Status)) {
      return (VOID*)(UINTN) Address;
    } else {
      return NULL;
    }
  }
  
  
  STATIC
  VOID
  SetupLinuxMemmap (
    IN OUT struct boot_params        *Bp
    )
  {
    EFI_STATUS                           Status;
    UINT8                                TmpMemoryMap[1];
    UINTN                                MapKey;
    UINTN                                DescriptorSize;
    UINT32                               DescriptorVersion;
    UINTN                                MemoryMapSize;
    EFI_MEMORY_DESCRIPTOR                *MemoryMap;
    EFI_MEMORY_DESCRIPTOR                *MemoryMapPtr;
    UINTN                                Index;
    struct efi_info                      *Efi;
    struct e820_entry                    *LastE820;
    struct e820_entry                    *E820;
    UINTN                                E820EntryCount;
    EFI_PHYSICAL_ADDRESS                 LastEndAddr;
  
    //
    // Get System MemoryMapSize
    //
    MemoryMapSize = sizeof (TmpMemoryMap);
    Status = gBS->GetMemoryMap (
                    &MemoryMapSize,
                    (EFI_MEMORY_DESCRIPTOR *)TmpMemoryMap,
                    &MapKey,
                    &DescriptorSize,
                    &DescriptorVersion
                    );
    ASSERT (Status == EFI_BUFFER_TOO_SMALL);
    //
    // Enlarge space here, because we will allocate pool now.
    //
    MemoryMapSize += EFI_PAGE_SIZE;
    Status = gBS->AllocatePool (
                    EfiLoaderData,
                    MemoryMapSize,
                    (VOID **) &MemoryMap
                    );
    ASSERT_EFI_ERROR (Status);
  
    //
    // Get System MemoryMap
    //
    Status = gBS->GetMemoryMap (
                    &MemoryMapSize,
                    MemoryMap,
                    &MapKey,
                    &DescriptorSize,
                    &DescriptorVersion
                    );
    ASSERT_EFI_ERROR (Status);
  
    LastE820 = NULL;
    E820 = &Bp->e820_map[0];
    E820EntryCount = 0;
    LastEndAddr = 0;
    MemoryMapPtr = MemoryMap;
    for (Index = 0; Index < (MemoryMapSize / DescriptorSize); Index++) {
      UINTN E820Type = 0;
  
      if (MemoryMap->NumberOfPages == 0) {
        continue;
      }
  
      switch(MemoryMap->Type) {
      case EfiReservedMemoryType:
      case EfiRuntimeServicesCode:
      case EfiRuntimeServicesData:
      case EfiMemoryMappedIO:
      case EfiMemoryMappedIOPortSpace:
      case EfiPalCode:
        E820Type = E820_RESERVED;
        break;
  
      case EfiUnusableMemory:
        E820Type = E820_UNUSABLE;
        break;
  
      case EfiACPIReclaimMemory:
        E820Type = E820_ACPI;
        break;
  
      case EfiLoaderCode:
      case EfiLoaderData:
      case EfiBootServicesCode:
      case EfiBootServicesData:
      case EfiConventionalMemory:
        E820Type = E820_RAM;
        break;
  
      case EfiACPIMemoryNVS:
        E820Type = E820_NVS;
        break;
  
      default:
        DEBUG ((
          EFI_D_ERROR,
          "Invalid EFI memory descriptor type (0x%x)!\n",
          MemoryMap->Type
          ));
        continue;
      }
  
      if ((LastE820 != NULL) &&
          (LastE820->type == (UINT32) E820Type) &&
          (MemoryMap->PhysicalStart == LastEndAddr)) {
        LastE820->size += EFI_PAGES_TO_SIZE ((UINTN) MemoryMap->NumberOfPages);
        LastEndAddr += EFI_PAGES_TO_SIZE ((UINTN) MemoryMap->NumberOfPages);
      } else {
        if (E820EntryCount >= (sizeof (Bp->e820_map) / sizeof (Bp->e820_map[0]))) {
          break;
        }
        E820->type = (UINT32) E820Type;
        E820->addr = MemoryMap->PhysicalStart;
        E820->size = EFI_PAGES_TO_SIZE ((UINTN) MemoryMap->NumberOfPages);
        LastE820 = E820;
        LastEndAddr = E820->addr + E820->size;
        E820++;
        E820EntryCount++;
      }
  
      //
      // Get next item
      //
      MemoryMap = (EFI_MEMORY_DESCRIPTOR *)((UINTN)MemoryMap + DescriptorSize);
    }
    Bp->e820_entries = (UINT8) E820EntryCount;
  
    Efi = &Bp->efi_info;
    Efi->efi_systab = (UINT32)(UINTN) gST;
    Efi->efi_memdesc_size = (UINT32) DescriptorSize;
    Efi->efi_memdesc_version = DescriptorVersion;
    Efi->efi_memmap = (UINT32)(UINTN) MemoryMapPtr;
    Efi->efi_memmap_size = (UINT32) MemoryMapSize;
  #ifdef MDE_CPU_IA32
    Efi->efi_loader_signature = SIGNATURE_32 ('E', 'L', '3', '2');
  #else
    Efi->efi_systab_hi = (UINT32) (((UINT64)(UINTN) gST) >> 32);
    Efi->efi_memmap_hi = (UINT32) (((UINT64)(UINTN) MemoryMapPtr) >> 32);
    Efi->efi_loader_signature = SIGNATURE_32 ('E', 'L', '6', '4');
  #endif
  
    gBS->ExitBootServices (gImageHandle, MapKey);
  }
  
  
  EFI_STATUS
  EFIAPI
  LoadLinuxSetCommandLine (
    IN OUT VOID    *KernelSetup,
    IN CHAR8       *CommandLine
    )
  {
    EFI_STATUS             Status;
    struct boot_params     *Bp;
  
    Status = BasicKernelSetupCheck (KernelSetup);
    if (EFI_ERROR (Status)) {
      return Status;
    }
  
    Bp = (struct boot_params*) KernelSetup;
  
    Bp->hdr.cmd_line_ptr = (UINT32)(UINTN) CommandLine;
  
    return EFI_SUCCESS;
  }
  
  
  EFI_STATUS
  EFIAPI
  LoadLinuxSetInitrd (
    IN OUT VOID    *KernelSetup,
    IN VOID        *Initrd,
    IN UINTN       InitrdSize
    )
  {
    EFI_STATUS             Status;
    struct boot_params     *Bp;
  
    Status = BasicKernelSetupCheck (KernelSetup);
    if (EFI_ERROR (Status)) {
      return Status;
    }
  
    Bp = (struct boot_params*) KernelSetup;
  
    Bp->hdr.ramdisk_start = (UINT32)(UINTN) Initrd;
    Bp->hdr.ramdisk_len = (UINT32) InitrdSize;
  
    return EFI_SUCCESS;
  }
  
  
  STATIC VOID
  FindBits (
    unsigned long Mask,
    UINT8 *Pos,
    UINT8 *Size
    )
  {
    UINT8 First, Len;
  
    First = 0;
    Len = 0;
  
    if (Mask) {
      while (!(Mask & 0x1)) {
        Mask = Mask >> 1;
        First++;
      }
  
      while (Mask & 0x1) {
        Mask = Mask >> 1;
        Len++;
      }
    }
    *Pos = First;
    *Size = Len;
  }
  
  
  STATIC
  EFI_STATUS
  SetupGraphicsFromGop (
    struct screen_info           *Si,
    EFI_GRAPHICS_OUTPUT_PROTOCOL *Gop
    )
  {
    EFI_GRAPHICS_OUTPUT_MODE_INFORMATION *Info;
    EFI_STATUS                           Status;
    UINTN                                Size;
  
    Status = Gop->QueryMode(Gop, Gop->Mode->Mode, &Size, &Info);
    if (EFI_ERROR (Status)) {
      return Status;
    }
  
    /* We found a GOP */
  
    /* EFI framebuffer */
    Si->orig_video_isVGA = 0x70;
  
    Si->orig_x = 0;
    Si->orig_y = 0;
    Si->orig_video_page = 0;
    Si->orig_video_mode = 0;
    Si->orig_video_cols = 0;
    Si->orig_video_lines = 0;
    Si->orig_video_ega_bx = 0;
    Si->orig_video_points = 0;
  
    Si->lfb_base = (UINT32) Gop->Mode->FrameBufferBase;
    Si->lfb_size = (UINT32) Gop->Mode->FrameBufferSize;
    Si->lfb_width = (UINT16) Info->HorizontalResolution;
    Si->lfb_height = (UINT16) Info->VerticalResolution;
    Si->pages = 1;
    Si->vesapm_seg = 0;
    Si->vesapm_off = 0;
  
    if (Info->PixelFormat == PixelRedGreenBlueReserved8BitPerColor) {
      Si->lfb_depth = 32;
      Si->red_size = 8;
      Si->red_pos = 0;
      Si->green_size = 8;
      Si->green_pos = 8;
      Si->blue_size = 8;
      Si->blue_pos = 16;
      Si->rsvd_size = 8;
      Si->rsvd_pos = 24;
      Si->lfb_linelength = (UINT16) (Info->PixelsPerScanLine * 4);
  
    } else if (Info->PixelFormat == PixelBlueGreenRedReserved8BitPerColor) {
      Si->lfb_depth = 32;
      Si->red_size = 8;
      Si->red_pos = 16;
      Si->green_size = 8;
      Si->green_pos = 8;
      Si->blue_size = 8;
      Si->blue_pos = 0;
      Si->rsvd_size = 8;
      Si->rsvd_pos = 24;
      Si->lfb_linelength = (UINT16) (Info->PixelsPerScanLine * 4);
    } else if (Info->PixelFormat == PixelBitMask) {
      FindBits(Info->PixelInformation.RedMask,
          &Si->red_pos, &Si->red_size);
      FindBits(Info->PixelInformation.GreenMask,
          &Si->green_pos, &Si->green_size);
      FindBits(Info->PixelInformation.BlueMask,
          &Si->blue_pos, &Si->blue_size);
      FindBits(Info->PixelInformation.ReservedMask,
          &Si->rsvd_pos, &Si->rsvd_size);
      Si->lfb_depth = Si->red_size + Si->green_size +
        Si->blue_size + Si->rsvd_size;
      Si->lfb_linelength = (UINT16) ((Info->PixelsPerScanLine * Si->lfb_depth) / 8);
    } else {
      Si->lfb_depth = 4;
      Si->red_size = 0;
      Si->red_pos = 0;
      Si->green_size = 0;
      Si->green_pos = 0;
      Si->blue_size = 0;
      Si->blue_pos = 0;
      Si->rsvd_size = 0;
      Si->rsvd_pos = 0;
      Si->lfb_linelength = Si->lfb_width / 2;
    }
  
    return Status;
  }
  
  
  STATIC
  EFI_STATUS
  SetupGraphics (
    IN OUT struct boot_params *Bp
    )
  {
    EFI_STATUS                      Status;
    EFI_HANDLE                      *HandleBuffer;
    UINTN                           HandleCount;
    UINTN                           Index;
    EFI_GRAPHICS_OUTPUT_PROTOCOL    *Gop;
  
    ZeroMem ((VOID*)&Bp->screen_info, sizeof(Bp->screen_info));
  
    Status = gBS->LocateHandleBuffer (
                    ByProtocol,
                    &gEfiGraphicsOutputProtocolGuid,
                    NULL,
                    &HandleCount,
                    &HandleBuffer
                    );
    if (!EFI_ERROR (Status)) {
      for (Index = 0; Index < HandleCount; Index++) {
        Status = gBS->HandleProtocol (
                        HandleBuffer[Index],
                        &gEfiGraphicsOutputProtocolGuid,
                        (VOID*) &Gop
                        );
        if (EFI_ERROR (Status)) {
          continue;
        }
  
        Status = SetupGraphicsFromGop (&Bp->screen_info, Gop);
        if (!EFI_ERROR (Status)) {
          FreePool (HandleBuffer);
          return EFI_SUCCESS;
        }
      }
  
      FreePool (HandleBuffer);
    }
  
    return EFI_NOT_FOUND;
  }
  
  
  STATIC
  EFI_STATUS
  SetupLinuxBootParams (
    IN OUT struct boot_params *Bp
    )
  {
    SetupGraphics (Bp);
  
    SetupLinuxMemmap (Bp);
  
    return EFI_SUCCESS;
  }
  
  
  EFI_STATUS
  EFIAPI
  LoadLinux (
    IN VOID      *Kernel,
    IN OUT VOID  *KernelSetup
    )
  {
    EFI_STATUS             Status;
    struct boot_params  *Bp;
  
    Status = BasicKernelSetupCheck (KernelSetup);
    if (EFI_ERROR (Status)) {
      return Status;
    }
  
    Bp = (struct boot_params *) KernelSetup;
  
    if (Bp->hdr.version < 0x205 || !Bp->hdr.relocatable_kernel) {
      //
      // We only support relocatable kernels
      //
      return EFI_UNSUPPORTED;
    }
  
    InitLinuxDescriptorTables ();
  
    Bp->hdr.code32_start = (UINT32)(UINTN) Kernel;
    if (Bp->hdr.version >= 0x20c && Bp->hdr.handover_offset &&
        (Bp->hdr.xloadflags & (sizeof (UINTN) == 4 ? BIT2 : BIT3))) {
      DEBUG ((EFI_D_INFO, "Jumping to kernel EFI handover point at ofs %x\n", Bp->hdr.handover_offset));
  
      DisableInterrupts ();
      JumpToUefiKernel ((VOID*) gImageHandle, (VOID*) gST, KernelSetup, Kernel);
    }
  
    //
    // Old kernels without EFI handover protocol
    //
    SetupLinuxBootParams (KernelSetup);
  
    DEBUG ((EFI_D_INFO, "Jumping to kernel\n"));
    DisableInterrupts ();
    SetLinuxDescriptorTables ();
    JumpToKernel (Kernel, (VOID*) KernelSetup);
  
    return EFI_SUCCESS;
  }
  
  