/** @file

  Copyright (c) 2008 - 2009, Apple Inc. All rights reserved.<BR>
  Copyright (c) 2013 - 2021, Arm Limited. All rights reserved.<BR>

  SPDX-License-Identifier: BSD-2-Clause-Patent

**/
#include <Base.h>

#include <Library/BaseLib.h>
#include <Library/SemihostLib.h>

#include "SemihostPrivate.h"

BOOLEAN
SemihostConnectionSupported (
  VOID
  )
{
  return SEMIHOST_SUPPORTED;
}

RETURN_STATUS
SemihostFileOpen (
  IN  CHAR8   *FileName,
  IN  UINT32  Mode,
  OUT UINTN   *FileHandle
  )
{
  SEMIHOST_FILE_OPEN_BLOCK  OpenBlock;
  INT32                     Result;

  if (FileHandle == NULL) {
    return RETURN_INVALID_PARAMETER;
  }

  // Remove any leading separator (e.g.: '\'). EFI Shell adds one.
  if (*FileName == '\\') {
    FileName++;
  }

  OpenBlock.FileName   = FileName;
  OpenBlock.Mode       = Mode;
  OpenBlock.NameLength = AsciiStrLen (FileName);

  Result = SEMIHOST_SYS_OPEN (&OpenBlock);

  if (Result == -1) {
    return RETURN_NOT_FOUND;
  } else {
    *FileHandle = Result;
    return RETURN_SUCCESS;
  }
}

RETURN_STATUS
SemihostFileSeek (
  IN UINTN  FileHandle,
  IN UINTN  Offset
  )
{
  SEMIHOST_FILE_SEEK_BLOCK  SeekBlock;
  INT32                     Result;

  SeekBlock.Handle   = FileHandle;
  SeekBlock.Location = Offset;

  Result = SEMIHOST_SYS_SEEK (&SeekBlock);

  // Semihosting does not behave as documented. It returns the offset on
  // success.
  if (Result < 0) {
    return RETURN_ABORTED;
  } else {
    return RETURN_SUCCESS;
  }
}

RETURN_STATUS
SemihostFileRead (
  IN     UINTN  FileHandle,
  IN OUT UINTN  *Length,
  OUT    VOID   *Buffer
  )
{
  SEMIHOST_FILE_READ_WRITE_BLOCK  ReadBlock;
  UINT32                          Result;

  if ((Length == NULL) || (Buffer == NULL)) {
    return RETURN_INVALID_PARAMETER;
  }

  ReadBlock.Handle = FileHandle;
  ReadBlock.Buffer = Buffer;
  ReadBlock.Length = *Length;

  Result = SEMIHOST_SYS_READ (&ReadBlock);

  if ((*Length != 0) && (Result == *Length)) {
    return RETURN_ABORTED;
  } else {
    *Length -= Result;
    return RETURN_SUCCESS;
  }
}

RETURN_STATUS
SemihostFileWrite (
  IN     UINTN  FileHandle,
  IN OUT UINTN  *Length,
  IN     VOID   *Buffer
  )
{
  SEMIHOST_FILE_READ_WRITE_BLOCK  WriteBlock;

  if ((Length == NULL) || (Buffer == NULL)) {
    return RETURN_INVALID_PARAMETER;
  }

  WriteBlock.Handle = FileHandle;
  WriteBlock.Buffer = Buffer;
  WriteBlock.Length = *Length;

  *Length = SEMIHOST_SYS_WRITE (&WriteBlock);

  if (*Length != 0) {
    return RETURN_ABORTED;
  } else {
    return RETURN_SUCCESS;
  }
}

RETURN_STATUS
SemihostFileClose (
  IN UINTN  FileHandle
  )
{
  if (SEMIHOST_SYS_CLOSE (&FileHandle) == -1) {
    return RETURN_INVALID_PARAMETER;
  } else {
    return RETURN_SUCCESS;
  }
}

RETURN_STATUS
SemihostFileLength (
  IN  UINTN  FileHandle,
  OUT UINTN  *Length
  )
{
  INT32  Result;

  if (Length == NULL) {
    return RETURN_INVALID_PARAMETER;
  }

  Result = SEMIHOST_SYS_FLEN (&FileHandle);

  if (Result == -1) {
    return RETURN_ABORTED;
  } else {
    *Length = Result;
    return RETURN_SUCCESS;
  }
}

/**
  Get a temporary name for a file from the host running the debug agent.

  @param[out]  Buffer      Pointer to the buffer where the temporary name has to
                           be stored
  @param[in]   Identifier  File name identifier (integer in the range 0 to 255)
  @param[in]   Length      Length of the buffer to store the temporary name

  @retval  RETURN_SUCCESS            Temporary name returned
  @retval  RETURN_INVALID_PARAMETER  Invalid buffer address
  @retval  RETURN_ABORTED            Temporary name not returned

**/
RETURN_STATUS
SemihostFileTmpName (
  OUT  VOID   *Buffer,
  IN   UINT8  Identifier,
  IN   UINTN  Length
  )
{
  SEMIHOST_FILE_TMPNAME_BLOCK  TmpNameBlock;
  INT32                        Result;

  if (Buffer == NULL) {
    return RETURN_INVALID_PARAMETER;
  }

  TmpNameBlock.Buffer     = Buffer;
  TmpNameBlock.Identifier = Identifier;
  TmpNameBlock.Length     = Length;

  Result = SEMIHOST_SYS_TMPNAME (&TmpNameBlock);

  if (Result != 0) {
    return RETURN_ABORTED;
  } else {
    return RETURN_SUCCESS;
  }
}

RETURN_STATUS
SemihostFileRemove (
  IN CHAR8  *FileName
  )
{
  SEMIHOST_FILE_REMOVE_BLOCK  RemoveBlock;
  UINT32                      Result;

  // Remove any leading separator (e.g.: '\'). EFI Shell adds one.
  if (*FileName == '\\') {
    FileName++;
  }

  RemoveBlock.FileName   = FileName;
  RemoveBlock.NameLength = AsciiStrLen (FileName);

  Result = SEMIHOST_SYS_REMOVE (&RemoveBlock);

  if (Result == 0) {
    return RETURN_SUCCESS;
  } else {
    return RETURN_ABORTED;
  }
}

/**
  Rename a specified file.

  @param[in]  FileName     Name of the file to rename.
  @param[in]  NewFileName  The new name of the file.

  @retval  RETURN_SUCCESS            File Renamed
  @retval  RETURN_INVALID_PARAMETER  Either the current or the new name is not specified
  @retval  RETURN_ABORTED            Rename failed

**/
RETURN_STATUS
SemihostFileRename (
  IN  CHAR8  *FileName,
  IN  CHAR8  *NewFileName
  )
{
  SEMIHOST_FILE_RENAME_BLOCK  RenameBlock;
  INT32                       Result;

  if ((FileName == NULL) || (NewFileName == NULL)) {
    return RETURN_INVALID_PARAMETER;
  }

  RenameBlock.FileName          = FileName;
  RenameBlock.FileNameLength    = AsciiStrLen (FileName);
  RenameBlock.NewFileName       = NewFileName;
  RenameBlock.NewFileNameLength = AsciiStrLen (NewFileName);

  Result = SEMIHOST_SYS_RENAME (&RenameBlock);

  if (Result != 0) {
    return RETURN_ABORTED;
  } else {
    return RETURN_SUCCESS;
  }
}

CHAR8
SemihostReadCharacter (
  VOID
  )
{
  return SEMIHOST_SYS_READC ();
}

VOID
SemihostWriteCharacter (
  IN CHAR8  Character
  )
{
  SEMIHOST_SYS_WRITEC (&Character);
}

VOID
SemihostWriteString (
  IN CHAR8  *String
  )
{
  SEMIHOST_SYS_WRITE0 (String);
}

UINT32
SemihostSystem (
  IN CHAR8  *CommandLine
  )
{
  SEMIHOST_SYSTEM_BLOCK  SystemBlock;

  SystemBlock.CommandLine   = CommandLine;
  SystemBlock.CommandLength = AsciiStrLen (CommandLine);

  return SEMIHOST_SYS_SYSTEM (&SystemBlock);
}
