/** @file

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

**/

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

#include "VirtHstiDxe.h"

#define WRITE_BYTE_CMD           0x10
#define BLOCK_ERASE_CMD          0x20
#define CLEAR_STATUS_CMD         0x50
#define READ_STATUS_CMD          0x70
#define READ_DEVID_CMD           0x90
#define BLOCK_ERASE_CONFIRM_CMD  0xd0
#define READ_ARRAY_CMD           0xff
#define CLEARED_ARRAY_STATUS     0x00

/* based on QemuFlashDetected (QemuFlashFvbServicesRuntimeDxe) */
UINT32
VirtHstiQemuFirmwareFlashCheck (
  UINT32  Address
  )
{
  volatile UINT8  *Ptr;

  UINTN  Offset;
  UINT8  OriginalUint8;
  UINT8  ProbeUint8;

  for (Offset = 0; Offset < EFI_PAGE_SIZE; Offset++) {
    Ptr        = (UINT8 *)(UINTN)(Address + Offset);
    ProbeUint8 = *Ptr;
    if ((ProbeUint8 != CLEAR_STATUS_CMD) &&
        (ProbeUint8 != READ_STATUS_CMD) &&
        (ProbeUint8 != CLEARED_ARRAY_STATUS))
    {
      break;
    }
  }

  if (Offset >= EFI_PAGE_SIZE) {
    DEBUG ((DEBUG_INFO, "%a: check failed\n", __func__));
    return QEMU_FIRMWARE_FLASH_UNKNOWN;
  }

  OriginalUint8 = *Ptr;
  *Ptr          = CLEAR_STATUS_CMD;
  ProbeUint8    = *Ptr;
  if ((OriginalUint8 != CLEAR_STATUS_CMD) &&
      (ProbeUint8 == CLEAR_STATUS_CMD))
  {
    *Ptr = OriginalUint8;
    DEBUG ((DEBUG_INFO, "%a: %p behaves as RAM\n", __func__, Ptr));
    return QEMU_FIRMWARE_FLASH_IS_RAM;
  }

  *Ptr       = READ_STATUS_CMD;
  ProbeUint8 = *Ptr;
  if (ProbeUint8 == OriginalUint8) {
    DEBUG ((DEBUG_INFO, "%a: %p behaves as ROM\n", __func__, Ptr));
    return QEMU_FIRMWARE_FLASH_IS_ROM;
  }

  if (ProbeUint8 == READ_STATUS_CMD) {
    *Ptr = OriginalUint8;
    DEBUG ((DEBUG_INFO, "%a: %p behaves as RAM\n", __func__, Ptr));
    return QEMU_FIRMWARE_FLASH_IS_RAM;
  }

  if (ProbeUint8 == CLEARED_ARRAY_STATUS) {
    *Ptr       = WRITE_BYTE_CMD;
    *Ptr       = OriginalUint8;
    *Ptr       = READ_STATUS_CMD;
    ProbeUint8 = *Ptr;
    *Ptr       = READ_ARRAY_CMD;
    if (ProbeUint8 & 0x10 /* programming error */) {
      DEBUG ((DEBUG_INFO, "%a: %p behaves as FLASH, write-protected\n", __func__, Ptr));
      return QEMU_FIRMWARE_FLASH_READ_ONLY;
    } else {
      DEBUG ((DEBUG_INFO, "%a: %p behaves as FLASH, writable\n", __func__, Ptr));
      return QEMU_FIRMWARE_FLASH_WRITABLE;
    }
  }

  DEBUG ((DEBUG_INFO, "%a: check failed\n", __func__));
  return QEMU_FIRMWARE_FLASH_UNKNOWN;
}
