/** @file Generic IPMI stack during PEI phase @copyright Copyright 2017 - 2021 Intel Corporation.
SPDX-License-Identifier: BSD-2-Clause-Patent **/ #include #include "PeiGenericIpmi.h" #include #include /////////////////////////////////////////////////////////////////////////////// // Function Implementations // /***************************************************************************** @brief Internal function @param[in] PeiServices General purpose services available to every PEIM. @retval EFI_SUCCESS Always return EFI_SUCCESS **/ EFI_STATUS EFIAPI PeiInitializeIpmiKcsPhysicalLayer ( IN CONST EFI_PEI_SERVICES **PeiServices ) { EFI_STATUS Status; PEI_IPMI_BMC_INSTANCE_DATA *mIpmiInstance; mIpmiInstance = NULL; // // Send Pre-Boot signal to BMC // if (PcdGetBool (PcdSignalPreBootToBmc)) { Status = SendPreBootSignaltoBmc (PeiServices); if (EFI_ERROR (Status)) { return Status; } } // // Enable OEM specific southbridge SIO KCS I/O address range 0xCA0 to 0xCAF at here // if the the I/O address range has not been enabled. // Status = PlatformIpmiIoRangeSet (PcdGet16 (PcdIpmiIoBaseAddress)); DEBUG ((DEBUG_INFO, "IPMI Peim:PlatformIpmiIoRangeSet - %r!\n", Status)); if (EFI_ERROR(Status)) { return Status; } mIpmiInstance = AllocateZeroPool (sizeof (PEI_IPMI_BMC_INSTANCE_DATA)); if (mIpmiInstance == NULL) { DEBUG ((EFI_D_ERROR,"IPMI Peim:EFI_OUT_OF_RESOURCES of memory allocation\n")); return EFI_OUT_OF_RESOURCES; } // // Calibrate TSC Counter. Stall for 10ms, then multiply the resulting number of // ticks in that period by 100 to get the number of ticks in a 1 second timeout // DEBUG ((DEBUG_INFO,"IPMI Peim:IPMI STACK Initialization\n")); mIpmiInstance->KcsTimeoutPeriod = (BMC_KCS_TIMEOUT_PEI *1000*1000) / KCS_DELAY_UNIT_PEI; DEBUG ((EFI_D_INFO,"IPMI Peim:KcsTimeoutPeriod = 0x%x\n", mIpmiInstance->KcsTimeoutPeriod)); // // Initialize IPMI IO Base. // mIpmiInstance->IpmiIoBase = PcdGet16 (PcdIpmiIoBaseAddress); DEBUG ((EFI_D_INFO,"IPMI Peim:IpmiIoBase=0x%x\n",mIpmiInstance->IpmiIoBase)); mIpmiInstance->Signature = SM_IPMI_BMC_SIGNATURE; mIpmiInstance->SlaveAddress = BMC_SLAVE_ADDRESS; mIpmiInstance->BmcStatus = BMC_NOTREADY; mIpmiInstance->IpmiTransportPpi.IpmiSubmitCommand = PeiIpmiSendCommand; mIpmiInstance->IpmiTransportPpi.GetBmcStatus = PeiGetIpmiBmcStatus; mIpmiInstance->PeiIpmiBmcDataDesc.Flags = EFI_PEI_PPI_DESCRIPTOR_PPI | EFI_PEI_PPI_DESCRIPTOR_TERMINATE_LIST; mIpmiInstance->PeiIpmiBmcDataDesc.Guid = &gPeiIpmiTransportPpiGuid; mIpmiInstance->PeiIpmiBmcDataDesc.Ppi = &mIpmiInstance->IpmiTransportPpi; // // Get the Device ID and check if the system is in Force Update mode. // Status = GetDeviceId (mIpmiInstance); if (EFI_ERROR (Status)) { DEBUG ((EFI_D_ERROR,"IPMI Peim:Get BMC Device Id Failed. Status=%r\n",Status)); } // // Do not continue initialization if the BMC is in Force Update Mode. // if (mIpmiInstance->BmcStatus == BMC_UPDATE_IN_PROGRESS || mIpmiInstance->BmcStatus == BMC_HARDFAIL) { return EFI_UNSUPPORTED; } // // Just produce PPI // Status = PeiServicesInstallPpi (&mIpmiInstance->PeiIpmiBmcDataDesc); if (EFI_ERROR (Status)) { return Status; } return EFI_SUCCESS; } /***************************************************************************** @bref PRE-BOOT signal will be sent in very early PEI phase, to enable necessary KCS access for host boot. @param[in] PeiServices General purpose services available to every PEIM. @retval EFI_SUCCESS Indicates that the signal is sent successfully. **/ EFI_STATUS SendPreBootSignaltoBmc ( IN CONST EFI_PEI_SERVICES **PeiServices ) { EFI_STATUS Status; EFI_PEI_CPU_IO_PPI *CpuIoPpi; UINT32 ProvisionPort = 0; UINT8 PreBoot = 0; // // Locate CpuIo service // CpuIoPpi = (**PeiServices).CpuIo; ProvisionPort = PcdGet32 (PcdSioMailboxBaseAddress) + MBXDAT_B; PreBoot = 0x01;// PRE-BOOT Status = CpuIoPpi->Io.Write ( PeiServices, CpuIoPpi, EfiPeiCpuIoWidthUint8, ProvisionPort, 1, &PreBoot ); if (EFI_ERROR (Status)) { DEBUG ((EFI_D_ERROR, "SendPreBootSignaltoBmc () Write PRE-BOOT Status=%r\n", Status)); return Status; } return EFI_SUCCESS; } /***************************************************************************** @bref The entry point of the Ipmi PEIM. Instals Ipmi PPI interface. @param FileHandle Handle of the file being invoked. @param PeiServices Describes the list of possible PEI Services. @retval EFI_SUCCESS Indicates that Ipmi initialization completed successfully. **/ EFI_STATUS PeimIpmiInterfaceInit ( IN EFI_PEI_FILE_HANDLE FileHandle, IN CONST EFI_PEI_SERVICES **PeiServices ) { EFI_STATUS Status; // // Performing Ipmi KCS physical layer initialization // Status = PeiInitializeIpmiKcsPhysicalLayer (PeiServices); return EFI_SUCCESS; } // PeimIpmiInterfaceInit() EFI_STATUS PeiIpmiSendCommand ( IN PEI_IPMI_TRANSPORT_PPI *This, IN UINT8 NetFunction, IN UINT8 Lun, IN UINT8 Command, IN UINT8 *CommandData, IN UINT32 CommandDataSize, IN OUT UINT8 *ResponseData, IN OUT UINT32 *ResponseDataSize ) /*++ Routine Description: Send Ipmi Command in the right mode: HECI or KCS, to the appropiate device, ME or BMC. Arguments: This - Pointer to IPMI protocol instance NetFunction - Net Function of command to send Lun - LUN of command to send Command - IPMI command to send CommandData - Pointer to command data buffer, if needed CommandDataSize - Size of command data buffer ResponseData - Pointer to response data buffer ResponseDataSize - Pointer to response data buffer size Returns: EFI_INVALID_PARAMETER - One of the input values is bad EFI_DEVICE_ERROR - IPMI command failed EFI_BUFFER_TOO_SMALL - Response buffer is too small EFI_UNSUPPORTED - Command is not supported by BMC EFI_SUCCESS - Command completed successfully --*/ { // // This Will be unchanged ( BMC/KCS style ) // return PeiIpmiSendCommandToBmc ( This, NetFunction, Lun, Command, CommandData, (UINT8) CommandDataSize, ResponseData, (UINT8 *) ResponseDataSize, NULL ); } // IpmiSendCommand() EFI_STATUS PeiGetIpmiBmcStatus ( IN PEI_IPMI_TRANSPORT_PPI *This, OUT BMC_STATUS *BmcStatus, OUT SM_COM_ADDRESS *ComAddress ) /*++ Routine Description: Updates the BMC status and returns the Com Address Arguments: This - Pointer to IPMI protocol instance BmcStatus - BMC status ComAddress - Com Address Returns: EFI_SUCCESS - Success --*/ { return PeiIpmiBmcStatus ( This, BmcStatus, ComAddress, NULL ); } EFI_STATUS GetDeviceId ( IN PEI_IPMI_BMC_INSTANCE_DATA *mIpmiInstance ) /*++ Routine Description: Execute the Get Device ID command to determine whether or not the BMC is in Force Update Mode. If it is, then report it to the error manager. Arguments: mIpmiInstance - Data structure describing BMC variables and used for sending commands StatusCodeValue - An array used to accumulate error codes for later reporting. ErrorCount - Counter used to keep track of error codes in StatusCodeValue Returns: Status --*/ { EFI_STATUS Status; UINT32 DataSize; SM_CTRL_INFO *pBmcInfo; UINTN Retries; // // Set up a loop to retry for up to PcdIpmiBmcReadyDelayTimer seconds. Calculate retries not timeout // so that in case KCS is not enabled and IpmiSendCommand() returns // immediately we will not wait all the PcdIpmiBmcReadyDelayTimer seconds. // Retries = PcdGet8 (PcdIpmiBmcReadyDelayTimer); // // Get the device ID information for the BMC. // DataSize = sizeof (mIpmiInstance->TempData); while (EFI_ERROR (Status = PeiIpmiSendCommand ( &mIpmiInstance->IpmiTransportPpi, IPMI_NETFN_APP, 0, IPMI_APP_GET_DEVICE_ID, NULL, 0, mIpmiInstance->TempData, &DataSize ))) { DEBUG ((EFI_D_ERROR, "[IPMI] BMC does not respond (status: %r), %d retries left\n", Status, Retries)); if (Retries-- == 0) { ReportStatusCode (EFI_ERROR_CODE | EFI_ERROR_MAJOR, EFI_COMPUTING_UNIT_FIRMWARE_PROCESSOR | EFI_CU_FP_EC_COMM_ERROR); mIpmiInstance->BmcStatus = BMC_HARDFAIL; return Status; } // // Handle the case that BMC FW still not enable KCS channel after AC cycle. just stall 1 second // MicroSecondDelay (1*1000*1000); } pBmcInfo = (SM_CTRL_INFO*) &mIpmiInstance->TempData[0]; DEBUG ((DEBUG_INFO, "[IPMI PEI] BMC Device ID: 0x%02X, firmware version: %d.%02X UpdateMode:%x\n", pBmcInfo->DeviceId, pBmcInfo->MajorFirmwareRev, pBmcInfo->MinorFirmwareRev, pBmcInfo->UpdateMode)); // // In OpenBMC, UpdateMode: the bit 7 of byte 4 in get device id command is used for the BMC status: // 0 means BMC is ready, 1 means BMC is not ready. // At the very beginning of BMC power on, the status is 1 means BMC is in booting process and not ready. It is not the flag for force update mode. // if (pBmcInfo->UpdateMode == BMC_READY) { mIpmiInstance->BmcStatus = BMC_OK; return EFI_SUCCESS; } else { // // Updatemode = 1 mean BMC is not ready, continue waiting. // while (Retries-- != 0) { MicroSecondDelay(1*1000*1000); //delay 1 seconds DEBUG ((DEBUG_INFO, "[IPMI PEI] UpdateMode Retries:%x \n",Retries)); Status = PeiIpmiSendCommand ( &mIpmiInstance->IpmiTransportPpi, IPMI_NETFN_APP, 0, IPMI_APP_GET_DEVICE_ID, NULL, 0, mIpmiInstance->TempData, &DataSize ); if (!EFI_ERROR (Status)) { pBmcInfo = (SM_CTRL_INFO*) &mIpmiInstance->TempData[0]; DEBUG ((DEBUG_INFO, "[IPMI PEI] UpdateMode Retries:%x pBmcInfo->UpdateMode:%x\n", Retries, pBmcInfo->UpdateMode)); if (pBmcInfo->UpdateMode == BMC_READY) { mIpmiInstance->BmcStatus = BMC_OK; return EFI_SUCCESS; } } } } mIpmiInstance->BmcStatus = BMC_HARDFAIL; return Status; } // GetDeviceId()