/*
 * This file and its contents are supplied under the terms of the
 * Common Development and Distribution License ("CDDL"), version 1.0.
 * You may only use this file in accordance with the terms of version
 * 1.0 of the CDDL.
 *
 * A full copy of the text of the CDDL should have accompanied this
 * source.  A copy of the CDDL is also available via the Internet at
 * http://www.illumos.org/license/CDDL.
 */

/*
 * Copyright 2016 Toomas Soome <tsoome@me.com>
 */

/*
 * Build smap like memory map from efi memmap.
 */

#include <stand.h>
#include <inttypes.h>
#include <efi.h>
#include <efilib.h>
#include <sys/param.h>
#include <sys/linker.h>
#include <sys/queue.h>
#include <sys/stddef.h>
#include <machine/metadata.h>
#include <machine/pc/bios.h>
#include "bootstrap.h"

struct smap_buf {
	struct bios_smap	sb_smap;
	STAILQ_ENTRY(smap_buf)	sb_bufs;
};

static struct bios_smap *smapbase;
static int smaplen;

/*
 * See "ACPI Specification Release 6.5 Errata A"
 * Table 15-6 (page 785), UEFI Memory Types and mapping to ACPI address
 * range types.
 */
static int
smap_type(int type)
{
	switch (type) {
	case EfiLoaderCode:
	case EfiLoaderData:
	case EfiBootServicesCode:
	case EfiBootServicesData:
	case EfiConventionalMemory:
		return (SMAP_TYPE_MEMORY);
	case EfiReservedMemoryType:
	case EfiRuntimeServicesCode:
	case EfiRuntimeServicesData:
	case EfiMemoryMappedIO:
	case EfiMemoryMappedIOPortSpace:
	case EfiPalCode:
	case EfiUnusableMemory:
		return (SMAP_TYPE_RESERVED);
	case EfiACPIReclaimMemory:
		return (SMAP_TYPE_ACPI_RECLAIM);
	case EfiACPIMemoryNVS:
		return (SMAP_TYPE_ACPI_NVS);
	}
	return (SMAP_TYPE_RESERVED);
}

void
efi_getsmap(void)
{
	UINTN size, desc_size, key;
	EFI_MEMORY_DESCRIPTOR *efi_mmap, *p;
	EFI_PHYSICAL_ADDRESS addr;
	EFI_STATUS status;
	STAILQ_HEAD(smap_head, smap_buf) head =
	    STAILQ_HEAD_INITIALIZER(head);
	struct smap_buf *cur, *next;
	int i, n, ndesc;
	int type = -1;

	size = 0;
	efi_mmap = NULL;
	status = BS->GetMemoryMap(&size, efi_mmap, &key, &desc_size, NULL);
	efi_mmap = malloc(size);
	status = BS->GetMemoryMap(&size, efi_mmap, &key, &desc_size, NULL);
	if (EFI_ERROR(status)) {
		printf("GetMemoryMap: error %lu\n", DECODE_ERROR(status));
		free(efi_mmap);
		return;
	}

	STAILQ_INIT(&head);
	n = 0;
	i = 0;
	p = efi_mmap;
	next = NULL;
	ndesc = size / desc_size;
	while (i < ndesc) {
		if (next == NULL) {
			next = malloc(sizeof (*next));
			if (next == NULL)
				break;

			next->sb_smap.base = p->PhysicalStart;
			next->sb_smap.length =
			    p->NumberOfPages << EFI_PAGE_SHIFT;
			/*
			 * ACPI 6.1 tells the lower memory should be
			 * reported as normal memory, so we enforce
			 * page 0 type even as vmware maps it as
			 * acpi reclaimable.
			 */
			if (next->sb_smap.base == 0)
				type = SMAP_TYPE_MEMORY;
			else
				type = smap_type(p->Type);
			next->sb_smap.type = type;

			STAILQ_INSERT_TAIL(&head, next, sb_bufs);
			n++;
			p = NextMemoryDescriptor(p, desc_size);
			i++;
			continue;
		}
		addr = next->sb_smap.base + next->sb_smap.length;
		if ((smap_type(p->Type) == type) &&
		    (p->PhysicalStart == addr)) {
			next->sb_smap.length +=
			    (p->NumberOfPages << EFI_PAGE_SHIFT);
			p = NextMemoryDescriptor(p, desc_size);
			i++;
		} else
			next = NULL;
	}
	smaplen = n;
	if (smaplen > 0) {
		smapbase = malloc(smaplen * sizeof (*smapbase));
		if (smapbase != NULL) {
			n = 0;
			STAILQ_FOREACH(cur, &head, sb_bufs)
				smapbase[n++] = cur->sb_smap;
		}
		cur = STAILQ_FIRST(&head);
		while (cur != NULL) {
			next = STAILQ_NEXT(cur, sb_bufs);
			free(cur);
			cur = next;
		}
	}
	free(efi_mmap);
}

void
efi_addsmapdata(struct preloaded_file *kfp)
{
	size_t size;

	if (smapbase == NULL || smaplen == 0)
		return;
	size = smaplen * sizeof (*smapbase);
	file_addmetadata(kfp, MODINFOMD_SMAP, size, smapbase);
}

COMMAND_SET(smap, "smap", "show BIOS SMAP", command_smap);

static int
command_smap(int argc __unused, char *argv[] __unused)
{
	uint_t i;

	if (smapbase == NULL || smaplen == 0)
		return (CMD_ERROR);

	for (i = 0; i < smaplen; i++)
		printf("SMAP type=%02" PRIx32 " base=%016" PRIx64
		    " len=%016" PRIx64 "\n", smapbase[i].type,
		    smapbase[i].base, smapbase[i].length);
	return (CMD_OK);
}
