/* ----------------------------------------------------------------------- *
 *
 *   Copyright 2007-2008 H. Peter Anvin - All Rights Reserved
 *   Copyright 2012 Intel Corporation; author: H. Peter Anvin
 *   Chandramouli Narayanan
 *
 *   This program 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, Inc., 53 Temple Place Ste 330,
 *   Boston MA 02111-1307, USA; either version 2 of the License, or
 *   (at your option) any later version; incorporated herein by reference.
 *
 * ----------------------------------------------------------------------- */

/* Miscellaneous functions for UEFI support
 * We assume that EFI library initialization has completed
 * and we have access to the global EFI exported variables
 *
 */
#include "efi.h"
#include "fio.h"

/* Variables that need to be exported
 * efi_errno - maintains the errors from EFI calls to display error messages.
 */
EFI_STATUS efi_errno = EFI_SUCCESS;

/* Locals
 * vol_root - handle to the root device for file operations
 */
static EFI_FILE_HANDLE vol_root;

/* Table of UEFI error messages to be indexed with the EFI errno
 * Update error message list as needed
 */
static CHAR16 *uefi_errmsg[] = {
	L"EFI_UNDEFINED",	/* should not get here */
	L"EFI_LOAD_ERROR",
	L"EFI_INVALID_PARAMETER",
	L"EFI_UNSUPPORTED",
	L"EFI_BAD_BUFFER_SIZE",
	L"EFI_BUFFER_TOO_SMALL",
	L"EFI_NOT_READY",
	L"EFI_DEVICE_ERROR",
	L"EFI_WRITE_PROTECTED",
	L"EFI_OUT_OF_RESOURCES",
	L"EFI_VOLUME_CORRUPTED",
	L"EFI_VOLUME_FULL",
	L"EFI_NO_MEDIA",
	L"EFI_MEDIA_CHANGED",
	L"EFI_NOT_FOUND",
	L"EFI_ACCESS_DENIED",
	L"EFI_NO_RESPONSE",
	L"EFI_NO_MAPPING",
	L"EFI_TIMEOUT",
	L"EFI_NOT_STARTED",
	L"EFI_ALREADY_STARTED",
	L"EFI_ABORTED",
	L"EFI_ICMP_ERROR",
	L"EFI_TFTP_ERROR",
	L"EFI_PROTOCOL_ERROR"
};

static UINTN nerrs = sizeof(uefi_errmsg)/sizeof(CHAR16 *);


/* Generic write error message; there is no gnu lib api to write to StdErr
 * For now, everything goes ConOut
 */
void efi_printerr(
    CHAR16   *fmt,
    ...
    )
{
    va_list     args;
    va_start (args, fmt);
    VPrint (fmt, args);
    va_end (args);
}

/* Simple console logger of efi-specific error messages. It uses
 * gnu-efi library Print function to do the job.
 */

void efi_perror(CHAR16 *prog)
{
	/* Ensure that the err number lies within range
	 * Beware: unsigned comparisons fail on efi, signed comparisons work
	 */
	if (EFI_ERROR(efi_errno) && (INTN)efi_errno < (INTN)nerrs)
		efi_printerr(L"%s: %s\n", prog, uefi_errmsg[efi_errno]);
}

/* Write to UEFI ConOut */
void efi_printout(
    CHAR16   *fmt,
    ...
    )
{
    va_list     args;
    va_start (args, fmt);
    VPrint (fmt, args);
    va_end (args);
}

/* IMPORTANT:
 * efi_setvol_root() needs to be called from efi main.
 * The rest of the ADV support relies on the file i/o environment
 * setup here. In order to use the EFI file support, we need
 * to set up the volume root. Subsequent file operations need the root to
 * access the interface routines.
 *
 */

EFI_STATUS efi_set_volroot(EFI_HANDLE device_handle)
{
	vol_root = LibOpenRoot(device_handle);
	if (!vol_root) {
		return EFI_DEVICE_ERROR;
	}
	return EFI_SUCCESS;
}

/* File operations using EFI runtime services */

/* Open the file using EFI runtime service
 * Opening a file in EFI requires a handle to the device
 * root in order to use the interface to the file operations supported by UEFI.
 * For now, assume device volume root handle from the loaded image
 *
 * Return a valid handle if open succeeded and null otherwise.
 * UEFI returns a bogus handle on error, so return null handle on error.
 *
 * TODO:
 * 1. Validate the assumption about the root device
 * 2. Can EFI open a file with full path name specification?
 * 3. Look into gnu-efi helper functions for dealing with device path/file path
 * 4. Consider utilizing EFI file open attributes.
 * 5. In EFI, file attributes can be specified only at the time of creation.
 * How do we support the equivalent of set_attributes() and clear_attributes()
 */
EFI_FILE_HANDLE efi_open(CHAR16 *file, UINT64 mode)
{
	/* initialize with NULL handle since EFI open returns bogus */
	EFI_FILE_HANDLE	fd = NULL;

	ASSERT(vol_root);

	/* Note that the attributes parameter is none for now */
	efi_errno = uefi_call_wrapper(vol_root->Open,
					5,
					vol_root,
					&fd,
					file,
					mode,
					0);
	return fd;
}

/*
 * read/write wrapper functions for UEFI
 *
 * Read or write the specified number of bytes starting at the
 * offset specified.
 *
 * Returns:
 * number of bytes read/written on success
 * -1 on error
 */
/* Wrapper function to read from a file */
size_t efi_xpread(EFI_FILE_HANDLE fd, void *buf, size_t count, off_t offset)
{
	ASSERT(fd);
	efi_errno = uefi_call_wrapper(fd->SetPosition,
					2,
				    fd,
				    offset);
	if (EFI_ERROR(efi_errno)) return -1;
	efi_errno = uefi_call_wrapper(fd->Read,
					3,
				    fd,
				    &count,
					buf);
	if (EFI_ERROR(efi_errno)) return -1;
	return count;
}

/* Wrapper function to write */
size_t efi_xpwrite(EFI_FILE_HANDLE fd, void *buf, size_t count, off_t offset)
{
	ASSERT(fd);
	efi_errno = uefi_call_wrapper(fd->SetPosition,
					2,
				    fd,
				    offset);
	if (EFI_ERROR(efi_errno)) return -1;
	efi_errno = uefi_call_wrapper(fd->Write,
					3,
				    fd,
				    &count,
					buf);
	if (EFI_ERROR(efi_errno)) return -1;
	return count;
}

/* For an open handle, return the generic file info excluding
 * the variable-length filename in the EFI_FILE_INFO structure.
 */
int efi_fstat(EFI_FILE_HANDLE fd, EFI_FILE_INFO *st)
{
	EFI_FILE_INFO *finfo;

	ASSERT(fd);
	finfo = LibFileInfo(fd);
	if (finfo) {
		uefi_call_wrapper(BS->CopyMem, 3, (VOID *)st, (VOID *)finfo, SIZE_OF_EFI_FILE_INFO);
		FreePool(finfo);
		return 0;
	}
	/* gnu-efi lib does not return EFI status; export a generic device error for now */
	efi_errno = EFI_DEVICE_ERROR;
	return -1;
}

/* set/clear_attributes()
 * 	Currently handles only VFAT filesystem
 * TODO:
 *    1. Assumes VFAT file system.
 *    2. How do we support other file systems?
 */
void efi_set_attributes(EFI_FILE_HANDLE fd)
{
	EFI_FILE_INFO *finfo;

	ASSERT(fd);
	finfo = LibFileInfo(fd);
	if (finfo) {
		/* Hidden+System+Readonly */
		finfo->Attribute = EFI_FILE_READ_ONLY|EFI_FILE_HIDDEN|EFI_FILE_SYSTEM;
		efi_errno = uefi_call_wrapper(fd->SetInfo,
					4,
					fd,
					&GenericFileInfo,
					finfo->Size,
					finfo);
		FreePool(finfo);
	} else efi_errno = EFI_NOT_FOUND;
}

void efi_clear_attributes(EFI_FILE_HANDLE fd)
{
	EFI_FILE_INFO *finfo;

	ASSERT(fd);
	finfo = LibFileInfo(fd);
	if (finfo) {
		finfo->Attribute = 0; /* no attributes */
		efi_errno = uefi_call_wrapper(fd->SetInfo, 
					4, 
					fd,
					&GenericFileInfo,
					finfo->Size,
					finfo);
		FreePool(finfo);
	} else efi_errno = EFI_NOT_FOUND;
}

/* Implement the sync operation using the EFI Flush file operation*/
void efi_sync(EFI_FILE_HANDLE fd)
{
	ASSERT(fd);
	efi_errno = uefi_call_wrapper(fd->Flush, 1, fd);
	return;
}

/* Close the file */
void efi_close(EFI_FILE_HANDLE fd)
{

	ASSERT(fd);
	efi_errno = uefi_call_wrapper(fd->Close, 1, fd);
	return;
}