/*++

Copyright (c) 2006  - 2014, Intel Corporation. All rights reserved.<BR>
                                                                                   

  This program and the accompanying materials are licensed and made available under

  the terms and conditions of the BSD License that accompanies this distribution.  

  The full text of the license may be found at                                     

  http://opensource.org/licenses/bsd-license.php.                                  

                                                                                   

  THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,            

  WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.    

                                                                                   



Module Name:

  MiscProcessorInformationFunction.c

Abstract:

  Onboard processor information boot time changes.
  SMBIOS type 4.

--*/

#include "CommonHeader.h"

#include "MiscSubclassDriver.h"

#include <Protocol/MpService.h>
#include <Protocol/DataHub.h>
#include <Guid/DataHubRecords.h>
#include <Library/CpuIA32.h>

#define EfiProcessorFamilyIntelAtomProcessor    0x2B

EFI_GUID                        mProcessorProducerGuid;


/**
  Get cache SMBIOS record handle.

  @param  Smbios        Pointer to SMBIOS protocol instance.
  @param  CacheLevel    Level of cache, starting from one.
  @param  Handle        Returned record handle.

**/

VOID
GetCacheHandle (
  IN  EFI_SMBIOS_PROTOCOL      *Smbios,
  IN  UINT8                    CacheLevel,
  OUT  EFI_SMBIOS_HANDLE       *Handle
  )
{
  UINT16                     CacheConfig;
  EFI_STATUS                 Status;
  EFI_SMBIOS_TYPE            RecordType;
  EFI_SMBIOS_TABLE_HEADER    *Buffer;

  *Handle = 0;
  RecordType = EFI_SMBIOS_TYPE_CACHE_INFORMATION;

  do {
    Status = Smbios->GetNext (
                       Smbios,
                       Handle,
                       &RecordType,
                       &Buffer,
                       NULL
                       );
    if (!EFI_ERROR(Status)) {
      CacheConfig = *(UINT16*)((UINT8*)Buffer + 5);
      if ((CacheConfig & 0x7) == (CacheLevel -1) ) {
        return;
      }
    }
  } while (!EFI_ERROR(Status));

  *Handle = 0xFFFF;
}


/**
  This function makes boot time changes to the contents of the
  MiscProcessorInformation (Type 4).

  @param  RecordData                 Pointer to copy of RecordData from the Data Table.

  @retval EFI_SUCCESS                All parameters were valid.
  @retval EFI_UNSUPPORTED            Unexpected RecordType value.
  @retval EFI_INVALID_PARAMETER      Invalid parameter was found.

**/
UINT32
ConvertBase10ToRaw (
  IN  EFI_EXP_BASE10_DATA             *Data)
{
  UINTN         Index;
  UINT32        RawData;

  RawData = Data->Value;
  for (Index = 0; Index < (UINTN) Data->Exponent; Index++) {
     RawData *= 10;
  }

  return  RawData;
}

#define BSEL_CR_OVERCLOCK_CONTROL	0xCD
#define	FUSE_BSEL_MASK				0x03



UINT16 miFSBFrequencyTable[4] = {
  83,          	// 83.3MHz
  100,          // 100MHz
  133,          // 133MHz
  117           // 116.7MHz
};

/**
  Determine the processor core frequency

  @param None

  @retval Processor core frequency multiplied by 3


**/
UINT16
DetermineiFsbFromMsr (
  VOID
  )
{
  //
  // Determine the processor core frequency
  //
  UINT64	Temp;
  Temp = (EfiReadMsr (BSEL_CR_OVERCLOCK_CONTROL)) & FUSE_BSEL_MASK;
  return miFSBFrequencyTable[(UINT32)(Temp)];

}

MISC_SMBIOS_TABLE_FUNCTION (MiscProcessorInformation)
{
    CHAR8                           *OptionalStrStart;
    EFI_STRING                      SerialNumber;
    CHAR16                          *Version=NULL;
    CHAR16                          *Manufacturer=NULL;
    CHAR16                          *Socket=NULL;
    CHAR16                          *AssetTag=NULL;
    CHAR16                          *PartNumber=NULL;
    UINTN                           SerialNumberStrLen=0;
    UINTN                           VersionStrLen=0;
    UINTN                           ManufacturerStrLen=0;
    UINTN                           SocketStrLen=0;
    UINTN                           AssetTagStrLen=0;
    UINTN                           PartNumberStrLen=0;
    UINTN                           ProcessorVoltage=0xAE;
    UINT32                          Eax01;
    UINT32                          Ebx01;
    UINT32                          Ecx01;
    UINT32                          Edx01;
    STRING_REF                      TokenToGet;
    EFI_STATUS                      Status;
    EFI_SMBIOS_HANDLE               SmbiosHandle;
    SMBIOS_TABLE_TYPE4              *SmbiosRecord;
    EFI_CPU_DATA_RECORD             *ForType4InputData;
    UINT16                          L1CacheHandle=0;
    UINT16                          L2CacheHandle=0;
    UINT16                          L3CacheHandle=0;
    UINTN                           NumberOfEnabledProcessors=0 ;
    UINTN                           NumberOfProcessors=0;
    UINT64                          Frequency = 0;
    EFI_MP_SERVICES_PROTOCOL        *MpService;
    EFI_DATA_HUB_PROTOCOL           *DataHub;
    UINT64                          MonotonicCount;
    EFI_DATA_RECORD_HEADER          *Record;
    EFI_SUBCLASS_TYPE1_HEADER       *DataHeader;
    UINT8                           *SrcData;
    UINT32                          SrcDataSize;
    EFI_PROCESSOR_VERSION_DATA      *ProcessorVersion;
    CHAR16                          *NewStringToken;
    STRING_REF                      TokenToUpdate;
    PROCESSOR_ID_DATA               *ProcessorId = NULL;


    //
    // First check for invalid parameters.
    //
    if (RecordData == NULL) {
        return EFI_INVALID_PARAMETER;
    }

    ForType4InputData = (EFI_CPU_DATA_RECORD *)RecordData;

    ProcessorId = AllocateZeroPool(sizeof(PROCESSOR_ID_DATA));
    if (ProcessorId == NULL) {
      return EFI_INVALID_PARAMETER;
    }

    //
    // Get the Data Hub Protocol. Assume only one instance
    //
    Status = gBS->LocateProtocol (
                    &gEfiDataHubProtocolGuid,
                    NULL,
                    (VOID **)&DataHub
                    );
    ASSERT_EFI_ERROR(Status);

    MonotonicCount = 0;
    Record = NULL;

    do {
      Status = DataHub->GetNextRecord (
                          DataHub,
                          &MonotonicCount,
                          NULL,
                          &Record
                          );
       if (!EFI_ERROR(Status)) {
         if (Record->DataRecordClass == EFI_DATA_RECORD_CLASS_DATA) {

            DataHeader  = (EFI_SUBCLASS_TYPE1_HEADER *)(Record + 1);
            SrcData     = (UINT8  *)(DataHeader + 1);
            SrcDataSize = Record->RecordSize - Record->HeaderSize - sizeof (EFI_SUBCLASS_TYPE1_HEADER);

            //
            // Processor
            //
            if (CompareGuid(&Record->DataRecordGuid, &gEfiProcessorSubClassGuid)) {
              CopyMem (&mProcessorProducerGuid, &Record->ProducerName, sizeof(EFI_GUID));
              switch (DataHeader->RecordType) {
                case ProcessorVoltageRecordType:
                  ProcessorVoltage = (((EFI_EXP_BASE10_DATA *)SrcData)->Value)/100 + 0x80;
                  break;
                case ProcessorCoreFrequencyRecordType:
                  DEBUG ((EFI_D_ERROR, "ProcessorCoreFrequencyRecordType SrcData1 =%d\n", ConvertBase10ToRaw((EFI_EXP_BASE10_DATA *)SrcData)/1000000));
                  Frequency = (ConvertBase10ToRaw((EFI_EXP_BASE10_DATA *)SrcData)/1000000);
                  break;
                case ProcessorVersionRecordType:
                  ProcessorVersion = (EFI_PROCESSOR_VERSION_DATA *)SrcData;
                  NewStringToken = HiiGetPackageString(&mProcessorProducerGuid, *ProcessorVersion, NULL);
                  TokenToUpdate = (STRING_REF)STR_MISC_PROCESSOR_VERSION;
                  HiiSetString(mHiiHandle, TokenToUpdate, NewStringToken, NULL);
                  break;
                default:
                  break;
              }
            }
          }
        }
    } while (!EFI_ERROR(Status) && (MonotonicCount != 0));

    //
    // Token to get for Socket Name
    //
    TokenToGet = STRING_TOKEN (STR_MISC_SOCKET_NAME);
    Socket = SmbiosMiscGetString (TokenToGet);
    SocketStrLen = StrLen(Socket);
    if (SocketStrLen > SMBIOS_STRING_MAX_LENGTH) {
         return EFI_UNSUPPORTED;
    }

    //
    // Token to get for Processor Manufacturer
    //
    TokenToGet = STRING_TOKEN (STR_MISC_PROCESSOR_MAUFACTURER);
    Manufacturer = SmbiosMiscGetString (TokenToGet);
    ManufacturerStrLen = StrLen(Manufacturer);
    if (ManufacturerStrLen > SMBIOS_STRING_MAX_LENGTH) {
      return EFI_UNSUPPORTED;
    }

    //
    // Token to get for Processor Version
    //
    TokenToGet = STRING_TOKEN (STR_MISC_PROCESSOR_VERSION);
    Version = SmbiosMiscGetString (TokenToGet);
    VersionStrLen = StrLen(Version);
    if (VersionStrLen > SMBIOS_STRING_MAX_LENGTH) {
        return EFI_UNSUPPORTED;
    }

    //
    // Token to get for Serial Number
    //
    TokenToGet = STRING_TOKEN (STR_MISC_PROCESSOR_SERIAL_NUMBER);
    SerialNumber = SmbiosMiscGetString (TokenToGet);
    SerialNumberStrLen = StrLen(SerialNumber);
    if (SerialNumberStrLen > SMBIOS_STRING_MAX_LENGTH) {
        return EFI_UNSUPPORTED;
    }

    //
    // Token to get for Assert Tag Information
    //
    TokenToGet = STRING_TOKEN (STR_MISC_ASSERT_TAG_DATA);
    AssetTag = SmbiosMiscGetString (TokenToGet);
    AssetTagStrLen = StrLen(AssetTag);
    if (AssetTagStrLen > SMBIOS_STRING_MAX_LENGTH) {
        return EFI_UNSUPPORTED;
    }

    //
    // Token to get for part number Information
    //
    TokenToGet = STRING_TOKEN (STR_MISC_PART_NUMBER);
    PartNumber = SmbiosMiscGetString (TokenToGet);
    PartNumberStrLen = StrLen(PartNumber);
    if (PartNumberStrLen > SMBIOS_STRING_MAX_LENGTH) {
         return EFI_UNSUPPORTED;
    }

    //
    // Two zeros following the last string.
    //
    SmbiosRecord = AllocateZeroPool(sizeof (SMBIOS_TABLE_TYPE4) + AssetTagStrLen + 1 + SocketStrLen + 1+ ManufacturerStrLen +1 + VersionStrLen+ 1+ SerialNumberStrLen + 1 + PartNumberStrLen+ 1 + 1);

    SmbiosRecord->Hdr.Type = EFI_SMBIOS_TYPE_PROCESSOR_INFORMATION;
    SmbiosRecord->Hdr.Length = sizeof (SMBIOS_TABLE_TYPE4);

    //
    // Make handle chosen by smbios protocol.add automatically.
    //
    SmbiosRecord->Hdr.Handle = 0;

    SmbiosRecord-> Socket= 1;
    SmbiosRecord -> ProcessorManufacture = 2;
    SmbiosRecord -> ProcessorVersion = 3;
    SmbiosRecord ->SerialNumber =4;

    SmbiosRecord-> AssetTag= 5;
    SmbiosRecord-> PartNumber= 6;

    //
    // Processor Type
    //
    ForType4InputData-> VariableRecord.ProcessorType= EfiCentralProcessor;
    SmbiosRecord -> ProcessorType = ForType4InputData-> VariableRecord.ProcessorType;

    //
    // Processor Family
    //
    ForType4InputData-> VariableRecord.ProcessorFamily= EfiProcessorFamilyIntelAtomProcessor; //0x2B;;
    SmbiosRecord -> ProcessorFamily = ForType4InputData-> VariableRecord.ProcessorFamily;
    SmbiosRecord -> ExternalClock = DetermineiFsbFromMsr();

    //
    // Processor ID
    //
    AsmCpuid(0x001, &Eax01, &Ebx01, &Ecx01, &Edx01);
    ProcessorId->Signature = *(PROCESSOR_SIGNATURE *)&Eax01;
    ProcessorId->FeatureFlags = *(PROCESSOR_FEATURE_FLAGS *)&Edx01;
    SmbiosRecord -> ProcessorId = *(PROCESSOR_ID_DATA *)ProcessorId;

    //
    // Processor Voltage
    //
    ForType4InputData-> VariableRecord.ProcessorVoltage= *(EFI_PROCESSOR_VOLTAGE_DATA *)&ProcessorVoltage;
    SmbiosRecord -> Voltage = *(PROCESSOR_VOLTAGE *) &(ForType4InputData-> VariableRecord.ProcessorVoltage);

    //
    // Status
    //
    ForType4InputData-> VariableRecord.ProcessorHealthStatus= 0x41;//0x41;
    SmbiosRecord -> Status = ForType4InputData-> VariableRecord.ProcessorHealthStatus;

    //
    // Processor Upgrade
    //
    SmbiosRecord -> ProcessorUpgrade = 0x008;

    //
    // Processor Family 2
    //
    SmbiosRecord -> ProcessorFamily2 = ForType4InputData-> VariableRecord.ProcessorFamily;

    //
    // Processor speed
    //
    SmbiosRecord-> CurrentSpeed = *(UINT16*) & Frequency;
    SmbiosRecord-> MaxSpeed = *(UINT16*) & Frequency;

    //
    // Processor Characteristics
    //
    AsmCpuid(0x8000000, NULL, NULL, NULL, &Edx01);
    Edx01= Edx01 >> 28;
    Edx01 &= 0x01;
    SmbiosRecord-> ProcessorCharacteristics= (UINT16)Edx01;

    //
    // Processor Core Count and Enabled core count
    //
    Status = gBS->LocateProtocol (
                    &gEfiMpServiceProtocolGuid,
                    NULL,
                    (void **)&MpService
                    );
    if (!EFI_ERROR (Status)) {
    //
    // Determine the number of processors
    //
    MpService->GetNumberOfProcessors (
                 MpService,
                 &NumberOfProcessors,
                 &NumberOfEnabledProcessors
                 );
    }
    SmbiosRecord-> CoreCount= (UINT8)NumberOfProcessors;
    SmbiosRecord-> EnabledCoreCount= (UINT8)NumberOfEnabledProcessors;
    SmbiosRecord-> ThreadCount= (UINT8)NumberOfEnabledProcessors;
    SmbiosRecord-> ProcessorCharacteristics = 0x2; // Unknown

    //
    // Processor Cache Handle
    //
    GetCacheHandle( Smbios,1, &L1CacheHandle);
    GetCacheHandle( Smbios,2, &L2CacheHandle);
    GetCacheHandle( Smbios,3, &L3CacheHandle);

    //
    // Updating Cache Handle Information
    //
    SmbiosRecord->L1CacheHandle  = L1CacheHandle;
    SmbiosRecord->L2CacheHandle  = L2CacheHandle;
    SmbiosRecord->L3CacheHandle  = L3CacheHandle;

    OptionalStrStart = (CHAR8 *)(SmbiosRecord + 1);
    UnicodeStrToAsciiStr(Socket, OptionalStrStart);
    UnicodeStrToAsciiStr(Manufacturer, OptionalStrStart + SocketStrLen + 1);
    UnicodeStrToAsciiStr(Version, OptionalStrStart + SocketStrLen + 1 + ManufacturerStrLen+ 1);
    UnicodeStrToAsciiStr(SerialNumber, OptionalStrStart + SocketStrLen + 1 + VersionStrLen + 1 + ManufacturerStrLen + 1);
    UnicodeStrToAsciiStr(AssetTag, OptionalStrStart + SerialNumberStrLen + 1 + VersionStrLen + 1 + ManufacturerStrLen + 1 + SocketStrLen + 1);
    UnicodeStrToAsciiStr(PartNumber, OptionalStrStart + SerialNumberStrLen + 1 + VersionStrLen + 1 + ManufacturerStrLen + 1 + SocketStrLen + 1 + AssetTagStrLen + 1 );

    //
    // Now we have got the full Smbios record, call Smbios protocol to add this record.
    //
    SmbiosHandle = SMBIOS_HANDLE_PI_RESERVED;
    Status = Smbios-> Add(
                        Smbios,
                        NULL,
                        &SmbiosHandle,
                        (EFI_SMBIOS_TABLE_HEADER *) SmbiosRecord
                        );
    if (EFI_ERROR (Status)) return Status;
    FreePool(SmbiosRecord);
    return Status;

}