/*
 * Copyright (c) 2018, Intel Corporation.
 * SPDX-License-Identifier: BSD-3-Clause
 */

#include <Uefi.h>
#include <NvmTypes.h>
#include <Library/UefiLib.h>
#include <Library/HiiLib.h>
#include <Library/BaseMemoryLib.h>
#include <Debug.h>
#include "NvmHealth.h"
#include <Protocol/DriverHealth.h>

/**
  Init sensors array with default values

  @param[in,out] DimmSensorsSet sensors array to fill with default values
**/
VOID
InitSensorsSet(
  IN OUT DIMM_SENSOR DimmSensorsSet[SENSOR_TYPE_COUNT]
  )
{
  UINT8 Index = 0;

  if (DimmSensorsSet == NULL) {
    return;
  }
  ZeroMem(DimmSensorsSet, sizeof(DIMM_SENSOR) * SENSOR_TYPE_COUNT);

  for (Index = 0; Index < SENSOR_TYPE_COUNT; ++Index) {
    DimmSensorsSet[Index].Type = Index;
    DimmSensorsSet[Index].Enabled = SENSOR_NA_ENABLED;
    DimmSensorsSet[Index].SettableThresholds = ThresholdNone;
    DimmSensorsSet[Index].SupportedThresholds = ThresholdNone;
  }

  DimmSensorsSet[SENSOR_TYPE_CONTROLLER_TEMPERATURE].SettableThresholds = AlarmThreshold;
  DimmSensorsSet[SENSOR_TYPE_CONTROLLER_TEMPERATURE].SupportedThresholds =
    AlarmThreshold | ShutdownThreshold;

  DimmSensorsSet[SENSOR_TYPE_MEDIA_TEMPERATURE].SettableThresholds = AlarmThreshold;
  DimmSensorsSet[SENSOR_TYPE_MEDIA_TEMPERATURE].SupportedThresholds =
    AlarmThreshold | ThrottlingStopThreshold | ThrottlingStartThreshold | ShutdownThreshold;

  DimmSensorsSet[SENSOR_TYPE_PERCENTAGE_REMAINING].SettableThresholds = AlarmThreshold;
  DimmSensorsSet[SENSOR_TYPE_PERCENTAGE_REMAINING].SupportedThresholds = AlarmThreshold;
}

EFI_STATUS
GetSensorsInfo(
  IN     EFI_DCPMM_CONFIG2_PROTOCOL *pNvmDimmConfigProtocol,
  IN     UINT16 DimmID,
  IN OUT DIMM_SENSOR DimmSensorsSet[SENSOR_TYPE_COUNT]
  )
{
  EFI_STATUS ReturnCode = EFI_SUCCESS;
  UINT8 Index = 0;
  SMART_AND_HEALTH_INFO HealthInfo;
  INT16 Threshold = 0;
  UINT8 DimmHealthState = 0;

  ZeroMem(&HealthInfo, sizeof(HealthInfo));

  /**
    Driver fills the data partially, so the initializer stays with the proper
    sensor types and default data.
  **/
  InitSensorsSet(DimmSensorsSet);

  ReturnCode = pNvmDimmConfigProtocol->GetSmartAndHealth(pNvmDimmConfigProtocol, DimmID, &HealthInfo);
  if (EFI_ERROR(ReturnCode)) {
    goto Finish;
  }

  /** Copy SMART & Health values **/
  DimmSensorsSet[SENSOR_TYPE_MEDIA_TEMPERATURE].Value = HealthInfo.MediaTemperature;
  DimmSensorsSet[SENSOR_TYPE_MEDIA_TEMPERATURE].ThrottlingStopThreshold = HealthInfo.MediaThrottlingStopThresh;
  DimmSensorsSet[SENSOR_TYPE_MEDIA_TEMPERATURE].ThrottlingStartThreshold = HealthInfo.MediaThrottlingStartThresh;
  DimmSensorsSet[SENSOR_TYPE_MEDIA_TEMPERATURE].ShutdownThreshold = HealthInfo.MediaTempShutdownThresh;
  DimmSensorsSet[SENSOR_TYPE_MEDIA_TEMPERATURE].MaxTemperature = HealthInfo.MaxMediaTemperature;
  DimmSensorsSet[SENSOR_TYPE_CONTROLLER_TEMPERATURE].Value = HealthInfo.ControllerTemperature;
  DimmSensorsSet[SENSOR_TYPE_CONTROLLER_TEMPERATURE].ShutdownThreshold = HealthInfo.ContrTempShutdownThresh;
  DimmSensorsSet[SENSOR_TYPE_CONTROLLER_TEMPERATURE].ThrottlingStopThreshold = HealthInfo.ControllerThrottlingStopThresh;
  DimmSensorsSet[SENSOR_TYPE_CONTROLLER_TEMPERATURE].ThrottlingStartThreshold = HealthInfo.ControllerThrottlingStartThresh;
  DimmSensorsSet[SENSOR_TYPE_CONTROLLER_TEMPERATURE].MaxTemperature = HealthInfo.MaxControllerTemperature;
  DimmSensorsSet[SENSOR_TYPE_PERCENTAGE_REMAINING].Value = HealthInfo.PercentageRemaining;
  DimmSensorsSet[SENSOR_TYPE_POWER_CYCLES].Value = HealthInfo.PowerCycles;
  DimmSensorsSet[SENSOR_TYPE_POWER_ON_TIME].Value = HealthInfo.PowerOnTime;
  DimmSensorsSet[SENSOR_TYPE_LATCHED_DIRTY_SHUTDOWN_COUNT].Value = HealthInfo.LatchedDirtyShutdownCount;
  DimmSensorsSet[SENSOR_TYPE_UNLATCHED_DIRTY_SHUTDOWN_COUNT].Value = HealthInfo.UnlatchedDirtyShutdownCount;
  DimmSensorsSet[SENSOR_TYPE_FW_ERROR_COUNT].Value = HealthInfo.MediaErrorCount + HealthInfo.ThermalErrorCount;
  DimmSensorsSet[SENSOR_TYPE_UP_TIME].Value = HealthInfo.UpTime;

  /** Determine Health State based on Health Status Bit Mask **/
  ConvertHealthBitmask(HealthInfo.HealthStatus, &DimmHealthState);
  DimmSensorsSet[SENSOR_TYPE_DIMM_HEALTH].Value = DimmHealthState;

  for (Index = SENSOR_TYPE_MEDIA_TEMPERATURE; Index <= SENSOR_TYPE_PERCENTAGE_REMAINING; ++Index) {
    ReturnCode = pNvmDimmConfigProtocol->GetAlarmThresholds(
        pNvmDimmConfigProtocol,
        DimmID,
        Index,
        &Threshold,
        &DimmSensorsSet[Index].Enabled,
        NULL);
    if (EFI_ERROR(ReturnCode)) {
      goto Finish;
    }

    DimmSensorsSet[Index].AlarmThreshold = Threshold;
  }
Finish:
  return ReturnCode;
}

/**
  Translate the SensorType into its Unicode string representation.
  The string buffer is static and the returned string is const so the
  caller should not make changes to the returned buffer.

  @param[in] SensorType the enum sensor type.
    The SensorTypeAll will result in an "Unknown" return as this value
    is not translatable.
**/
CONST
CHAR16 *
SensorTypeToString(
  IN     UINT8 SensorType
  )
{
  switch (SensorType) {
    case SENSOR_TYPE_MEDIA_TEMPERATURE:
      return MEDIA_TEMPERATURE_STR;
    case SENSOR_TYPE_CONTROLLER_TEMPERATURE:
      return CONTROLLER_TEMPERATURE_STR;
    case SENSOR_TYPE_PERCENTAGE_REMAINING:
      return SPARE_CAPACITY_STR;
    case SENSOR_TYPE_POWER_CYCLES:
      return POWER_CYCLES_STR;
    case SENSOR_TYPE_POWER_ON_TIME:
      return POWER_ON_TIME_STR;
    case SENSOR_TYPE_LATCHED_DIRTY_SHUTDOWN_COUNT:
      return LATCHED_DIRTY_SHUTDOWN_COUNT_STR;
    case SENSOR_TYPE_FW_ERROR_COUNT:
      return FW_ERROR_COUNT_STR;
    case SENSOR_TYPE_UP_TIME:
      return UPTIME_STR;
    case SENSOR_TYPE_DIMM_HEALTH:
      return DIMM_HEALTH_STR;
    case SENSOR_TYPE_UNLATCHED_DIRTY_SHUTDOWN_COUNT:
      return UNLATCHED_DIRTY_SHUTDOWN_COUNT_STR;
    default:
      return L"Unknown";
  }
}

/**
  Assign unit of measure for each SensorType.

  @param[in] SensorType the enum sensor type.
    Default case provides scalar.
**/
CONST
CHAR16 *
SensorValueMeasure(
  IN     UINT8 SensorType
  )
{
  switch(SensorType) {
    case SENSOR_TYPE_MEDIA_TEMPERATURE:
    case SENSOR_TYPE_CONTROLLER_TEMPERATURE:
      return TEMPERATURE_MSR;
    case SENSOR_TYPE_PERCENTAGE_REMAINING:
      return SPARE_CAPACITY_MSR;
    case SENSOR_TYPE_POWER_ON_TIME:
    case SENSOR_TYPE_UP_TIME:
      return TIME_MSR;
    default:
      return L"";
  }
}

/**
  Translate the SensorThresholdsType into its Unicode string representation.
  A returned string has to be freed by the caller.
**/
CHAR16 *
SensorThresholdsToString(
  IN     SensorThresholds SensorThresholdsType
)
{
  CHAR16 *pStr = NULL;
  CHAR16 *pFormat = NULL;

  if (SensorThresholdsType == ThresholdNone) {
    pStr = CatSPrintClean(pStr, FORMAT_STR, THRESHOLD_NONE_STR);
  }
  else {
    if ((SensorThresholdsType & AlarmThreshold) != 0) {
      pFormat = (pStr == NULL) ? FORMAT_STR : L"," FORMAT_STR;
      pStr = CatSPrintClean(pStr, pFormat, THRESHOLD_ALARM_STR);
    }

    if ((SensorThresholdsType & ThrottlingStopThreshold) != 0) {
      pFormat = (pStr == NULL) ? FORMAT_STR : L"," FORMAT_STR;
      pStr = CatSPrintClean(pStr, pFormat, THRESHOLD_THROTTLING_STOP_STR);
    }

    if ((SensorThresholdsType & ThrottlingStartThreshold) != 0) {
      pFormat = (pStr == NULL) ? FORMAT_STR : L"," FORMAT_STR;
      pStr = CatSPrintClean(pStr, pFormat, THRESHOLD_THROTTLING_START_STR);
    }

    if ((SensorThresholdsType & ShutdownThreshold) != 0) {
      pFormat = (pStr == NULL) ? FORMAT_STR : L"," FORMAT_STR;
      pStr = CatSPrintClean(pStr, pFormat, THRESHOLD_SHUTDOWN_STR);
    }
  }

  return pStr;
}

/**
  Translate the Enabled into its Unicode string representation.
  The string buffer is static and the returned string is const so the
  caller should not make changes to the returned buffer.
**/
CONST
CHAR16 *
SensorEnabledStateToString(
  IN     UINT8 SensorState
  )
{
  switch (SensorState) {
    case SENSOR_ENABLED:
      return SENSOR_ENABLED_STATE_ENABLED_STR;
    case SENSOR_DISABLED:
      return SENSOR_ENABLED_STATE_DISABLED_STR;
    case SENSOR_NA_ENABLED:
      return NOT_APPLICABLE_SHORT_STR;
    default:
      return L"Unknown";
  }
}
/**
  Convert Health state bitmask to a defined state

  @param[in] HealthMask - mask from DIMM structure
  @param[out] pHealthState - pointer to output with defined Health State
**/
VOID
ConvertHealthBitmask(
  IN     UINT8 HealthMask,
     OUT UINT8 *pHealthState
  )
{
  if (HealthMask & HealthStatusFatal) {
    *pHealthState = HEALTH_FATAL_FAILURE;
  } else if (HealthMask & HealthStatusCritical) {
    *pHealthState = HEALTH_CRITICAL_FAILURE;
  } else if (HealthMask & HealthStatusNoncritical) {
    *pHealthState = HEALTH_NON_CRITICAL_FAILURE;
  }  else if (HealthMask == CONTROLLER_HEALTH_NORMAL) {
    *pHealthState = HEALTH_HEALTHY;
  } else {
    *pHealthState = HEALTH_UNKNOWN;
  }
}

/**
  Convert dimm or sensor health state to a string. The caller is responsible for
  freeing the returned string

  @param[in] HiiHandle handle to the HII database that contains i18n strings
  @param[in] Health State - Numeric Value of the Health State.
      Defined in NvmTypes.h

  @retval String representation of the health state
**/
EFI_STRING
HealthToString(
  IN     EFI_HANDLE HiiHandle,
  IN     UINT8 HealthState
  )
{
  switch (HealthState) {
    case HEALTH_HEALTHY:
      return HiiGetString(HiiHandle, STRING_TOKEN(STR_HEALTHY), NULL);
    case HEALTH_NON_CRITICAL_FAILURE:
      return HiiGetString(HiiHandle, STRING_TOKEN(STR_NON_CRITICAL_FAILURE), NULL);
    case HEALTH_CRITICAL_FAILURE:
      return HiiGetString(HiiHandle, STRING_TOKEN(STR_CRITICAL_FAILURE), NULL);
    case HEALTH_FATAL_FAILURE:
      return HiiGetString(HiiHandle, STRING_TOKEN(STR_FATAL_FAILURE), NULL);
    case HEALTH_UNMANAGEABLE:
      return HiiGetString(HiiHandle, STRING_TOKEN(STR_UNMANAGEABLE), NULL);
    case HEALTH_NON_FUNCTIONAL:
      return HiiGetString(HiiHandle, STRING_TOKEN(STR_NON_FUNCTIONAL), NULL);
    case HEALTH_UNKNOWN:
    default:
      return HiiGetString(HiiHandle, STRING_TOKEN(STR_UNKNOWN), NULL);
  }
}
