/* lsefimemmap.c  - Display memory map.  */
/*
 *  GRUB  --  GRand Unified Bootloader
 *  Copyright (C) 2008  Free Software Foundation, Inc.
 *
 *  GRUB is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  GRUB is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with GRUB.  If not, see <http://www.gnu.org/licenses/>.
 */
#include <grub/types.h>
#include <grub/mm.h>
#include <grub/misc.h>
#include <grub/efi/api.h>
#include <grub/efi/efi.h>
#include <grub/command.h>

GRUB_MOD_LICENSE ("GPLv3+");

#define ADD_MEMORY_DESCRIPTOR(desc, size)	\
  ((grub_efi_memory_descriptor_t *) ((char *) (desc) + (size)))

static grub_err_t
grub_cmd_lsefimmap (grub_command_t cmd __attribute__ ((unused)),
		    int argc __attribute__ ((unused)),
		    char **args __attribute__ ((unused)))
{
  grub_efi_uintn_t map_size;
  grub_efi_memory_descriptor_t *memory_map;
  grub_efi_memory_descriptor_t *memory_map_end;
  grub_efi_memory_descriptor_t *desc;
  grub_efi_uintn_t desc_size;

  map_size = 0;
  if (grub_efi_get_memory_map (&map_size, NULL, NULL, &desc_size, 0) < 0)
    return 0;

  memory_map = grub_malloc (map_size);
  if (memory_map == NULL)
    return grub_errno;
  if (grub_efi_get_memory_map (&map_size, memory_map, NULL, &desc_size, 0) <= 0)
    goto fail;

  grub_printf
    ("Type      Physical start  - end             #Pages   "
     "     Size Attributes\n");
  memory_map_end = ADD_MEMORY_DESCRIPTOR (memory_map, map_size);
  for (desc = memory_map;
       desc < memory_map_end;
       desc = ADD_MEMORY_DESCRIPTOR (desc, desc_size))
    {
      grub_efi_uint64_t size;
      grub_efi_uint64_t attr;
      static const char types_str[][9] =
	{
	  "reserved",
	  "ldr-code",
	  "ldr-data",
	  "BS-code ",
	  "BS-data ",
	  "RT-code ",
	  "RT-data ",
	  "conv-mem",
	  "unusable",
	  "ACPI-rec",
	  "ACPI-nvs",
	  "MMIO    ",
	  "IO-ports",
	  "PAL-code",
	  "persist ",
	};
      if (desc->type < ARRAY_SIZE (types_str))
	grub_printf ("%s ", types_str[desc->type]);
      else
	grub_printf ("Unk %02x   ", desc->type);

      grub_printf (" %016" PRIxGRUB_UINT64_T "-%016" PRIxGRUB_UINT64_T
		   " %08" PRIxGRUB_UINT64_T,
		   desc->physical_start,
		   desc->physical_start + (desc->num_pages << 12) - 1,
		   desc->num_pages);

      size = desc->num_pages << 12;	/* 4 KiB page size */
      /*
       * Since size is a multiple of 4 KiB, no need to handle units
       * of just Bytes (which would use a mask of 0x3ff).
       *
       * 14 characters would support the largest possible number of 4 KiB
       * pages that are not a multiple of larger units (e.g., MiB):
       * 17592186044415 (0xffffff_fffff000), but that uses a lot of
       * whitespace for a rare case.  6 characters usually suffices;
       * columns will be off if not, but this is preferable to rounding.
       */
      if (size & 0xfffff)
	grub_printf (" %6" PRIuGRUB_UINT64_T "KiB", size >> 10);
      else if (size & 0x3fffffff)
	grub_printf (" %6" PRIuGRUB_UINT64_T "MiB", size >> 20);
      else if (size & 0xffffffffff)
	grub_printf (" %6" PRIuGRUB_UINT64_T "GiB", size >> 30);
      else if (size & 0x3ffffffffffff)
	grub_printf (" %6" PRIuGRUB_UINT64_T "TiB", size >> 40);
      else if (size & 0xfffffffffffffff)
	grub_printf (" %6" PRIuGRUB_UINT64_T "PiB", size >> 50);
      else
	grub_printf (" %6" PRIuGRUB_UINT64_T "EiB", size >> 60);

      attr = desc->attribute;
      if (attr & GRUB_EFI_MEMORY_RUNTIME)
	grub_printf (" RT");
      if (attr & GRUB_EFI_MEMORY_UC)
	grub_printf (" UC");
      if (attr & GRUB_EFI_MEMORY_WC)
	grub_printf (" WC");
      if (attr & GRUB_EFI_MEMORY_WT)
	grub_printf (" WT");
      if (attr & GRUB_EFI_MEMORY_WB)
	grub_printf (" WB");
      if (attr & GRUB_EFI_MEMORY_UCE)
	grub_printf (" UCE");
      if (attr & GRUB_EFI_MEMORY_WP)
	grub_printf (" WP");
      if (attr & GRUB_EFI_MEMORY_RP)
	grub_printf (" RP");
      if (attr & GRUB_EFI_MEMORY_XP)
	grub_printf (" XP");
      if (attr & GRUB_EFI_MEMORY_NV)
	grub_printf (" NV");
      if (attr & GRUB_EFI_MEMORY_MORE_RELIABLE)
	grub_printf (" MR");
      if (attr & GRUB_EFI_MEMORY_RO)
	grub_printf (" RO");

      grub_printf ("\n");
    }

 fail:
  grub_free (memory_map);
  return 0;
}

static grub_command_t cmd;

GRUB_MOD_INIT(lsefimmap)
{
  cmd = grub_register_command ("lsefimmap", grub_cmd_lsefimmap,
			       "", "Display EFI memory map.");
}

GRUB_MOD_FINI(lsefimmap)
{
  grub_unregister_command (cmd);
}
