/** @file GOP buffer and pixel size utility methods, and GOP burst mode caching code. Copyright (C) 2023, Mike Beaton. All rights reserved.
SPDX-License-Identifier: BSD-3-Clause **/ #include "OcConsoleLibInternal.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #define PAT_INDEX_TO_CHANGE 7 EFI_STATUS OcGopModeBytesPerPixel ( IN EFI_GRAPHICS_OUTPUT_PROTOCOL_MODE *Mode, OUT UINTN *BytesPerPixel ) { UINT32 MergedMasks; if ( (Mode == NULL) || (Mode->Info == NULL)) { ASSERT ( (Mode != NULL) && (Mode->Info != NULL) ); return EFI_UNSUPPORTED; } // // This can occur without PixelBltOnly, including in rotated DirectGopRendering - // see comment about PixelFormat in ConsoleGop.c RotateMode method. // if (Mode->FrameBufferBase == 0ULL) { return EFI_UNSUPPORTED; } switch (Mode->Info->PixelFormat) { case PixelRedGreenBlueReserved8BitPerColor: case PixelBlueGreenRedReserved8BitPerColor: *BytesPerPixel = sizeof (EFI_GRAPHICS_OUTPUT_BLT_PIXEL); return EFI_SUCCESS; case PixelBitMask: break; case PixelBltOnly: return EFI_UNSUPPORTED; default: return EFI_INVALID_PARAMETER; } MergedMasks = Mode->Info->PixelInformation.RedMask || Mode->Info->PixelInformation.GreenMask || Mode->Info->PixelInformation.BlueMask || Mode->Info->PixelInformation.ReservedMask; if (MergedMasks == 0) { return EFI_INVALID_PARAMETER; } *BytesPerPixel = (UINT32)((HighBitSet32 (MergedMasks) + 7) / 8); return EFI_SUCCESS; } EFI_STATUS OcGopModeSafeFrameBufferSize ( IN EFI_GRAPHICS_OUTPUT_PROTOCOL_MODE *Mode, OUT UINTN *FrameBufferSize ) { EFI_STATUS Status; UINTN BytesPerPixel; if ( (Mode == NULL) || (Mode->Info == NULL)) { ASSERT ( (Mode != NULL) && (Mode->Info != NULL) ); return EFI_UNSUPPORTED; } Status = OcGopModeBytesPerPixel (Mode, &BytesPerPixel); if (EFI_ERROR (Status)) { return Status; } if (BaseOverflowTriMulUN ( Mode->Info->PixelsPerScanLine, Mode->Info->VerticalResolution, BytesPerPixel, FrameBufferSize )) { return EFI_INVALID_PARAMETER; } return EFI_SUCCESS; } // // Use PAT to enable write-combining caching (burst mode) on GOP memory, // when it is suppported but firmware has not set it up. // STATIC EFI_STATUS WriteCombineGop ( EFI_GRAPHICS_OUTPUT_PROTOCOL *Gop, BOOLEAN SetPatWC ) { EFI_STATUS Status; MTRR_MEMORY_CACHE_TYPE MtrrCacheType; PAGE_MAP_AND_DIRECTORY_POINTER *PageTable; EFI_VIRTUAL_ADDRESS VirtualAddress; EFI_PHYSICAL_ADDRESS PhysicalAddress; UINTN FrameBufferSize; UINT64 Bits; UINT8 Level; BOOLEAN HasMtrr; BOOLEAN HasPat; UINT64 PatMsr; UINT64 ModifiedPatMsr; PAT_INDEX PatIndex; BOOLEAN HasDefaultPat; BOOLEAN AlreadySet; if (!SetPatWC) { return EFI_SUCCESS; } if ( (Gop == NULL) || (Gop->Mode == NULL) || (Gop->Mode->Info == NULL)) { ASSERT ( (Gop != NULL) && (Gop->Mode != NULL) && (Gop->Mode->Info != NULL) ); return EFI_UNSUPPORTED; } Status = OcGopModeSafeFrameBufferSize (Gop->Mode, &FrameBufferSize); if (EFI_ERROR (Status)) { return Status; } HasMtrr = IsMtrrSupported (); HasPat = OcIsPatSupported (); DEBUG (( DEBUG_INFO, "OCC: MTRR %asupported, PAT %asupported\n", HasMtrr ? "" : "not ", HasPat ? "" : "not " )); if (!HasMtrr || !HasPat) { return EFI_UNSUPPORTED; } AlreadySet = FALSE; PageTable = OcGetCurrentPageTable (NULL); do { PatMsr = AsmReadMsr64 (MSR_IA32_CR_PAT); HasDefaultPat = (PatMsr == PAT_DEFAULTS); DEBUG (( DEBUG_INFO, "OCC: PAT 0x%016LX (%a)\n", PatMsr, HasDefaultPat ? "default" : "not default" )); MtrrCacheType = MtrrGetMemoryAttribute (Gop->Mode->FrameBufferBase); VirtualAddress = Gop->Mode->FrameBufferBase; Status = OcGetSetPageTableInfoForAddress ( PageTable, VirtualAddress, &PhysicalAddress, &Level, &Bits, &PatIndex, FALSE ); DEBUG (( DEBUG_INFO, "OCC: 0x%LX MTRR %u=%a PTE%u bits 0x%016LX PAT@%u->%u=%a - %r\n", VirtualAddress, MtrrCacheType, OcGetMtrrTypeString (MtrrCacheType), Level, Bits, PatIndex.Index, GET_PAT_N (PatMsr, PatIndex.Index), OcGetPatTypeString (GET_PAT_N (PatMsr, PatIndex.Index)), Status )); if (EFI_ERROR (Status)) { ASSERT (!AlreadySet); return Status; } ASSERT (VirtualAddress == PhysicalAddress); if (AlreadySet) { break; } // // Attempting to set again if set in PAT works on some systems (including if set // before by us) but fails with a hang on some others, so avoid it even though we // might otherwise prefer to make sure to set the whole memory for the current mode. // Definitely no need to set again if set in MTRR. // if ( (MtrrCacheType == CacheWriteCombining) || (GET_PAT_N (PatMsr, PatIndex.Index) == PatWriteCombining) ) { return EFI_ALREADY_STARTED; } // // We cannot split MTRR ranges (returns out of resources, meaning there are not // enough MTRRs to keep the existing mapping and insert a new one), so set up // PAT7 as write combining (WC) and modify the page tables instead. // if (HasDefaultPat) { // // Modify PAT MSR to add WC entry somewhere reasonable (not first four entries, // and should use an entry likely to be unused) in PAT MSR. // TODO: Could fully check existing page tables to confirm PAT7 is unused. // ModifiedPatMsr = MODIFY_PAT_MSR (PatMsr, PAT_INDEX_TO_CHANGE, PatWriteCombining); DEBUG ((DEBUG_INFO, "OCC: Writing PAT 0x%016LX\n", ModifiedPatMsr)); AsmWriteMsr64 (MSR_IA32_CR_PAT, ModifiedPatMsr); PatIndex.Index = PAT_INDEX_TO_CHANGE; } else { // // Do not modify non-default PAT MSR, but use WC if it already exists there // (including previously set by us, if GOP mode changes more than once). // for (PatIndex.Index = 0; PatIndex.Index < PAT_INDEX_MAX; PatIndex.Index++) { if (GET_PAT_N (PatMsr, PatIndex.Index) == PatWriteCombining) { break; } } if (PatIndex.Index == PAT_INDEX_MAX) { Status = EFI_UNSUPPORTED; DEBUG ((DEBUG_INFO, "OCC: Non-default PAT MSR with no WC entry - %r\n", Status)); return Status; } } // // TODO: Use full GOP memory range not just range in use for current mode? // Status = OcSetPatIndexForAddressRange ( PageTable, Gop->Mode->FrameBufferBase, FrameBufferSize, &PatIndex ); if (EFI_ERROR (Status)) { DEBUG ((DEBUG_ERROR, "OCC: Failed to set PAT index for range - %r\n", Status)); return Status; } DEBUG_CODE_BEGIN (); AlreadySet = TRUE; DEBUG_CODE_END (); } while (AlreadySet); return EFI_SUCCESS; } EFI_STATUS OcSetGopBurstMode ( VOID ) { EFI_STATUS Status; EFI_GRAPHICS_OUTPUT_PROTOCOL *Gop; Status = gBS->HandleProtocol ( gST->ConsoleOutHandle, &gEfiGraphicsOutputProtocolGuid, (VOID **)&Gop ); if (EFI_ERROR (Status)) { DEBUG ((DEBUG_INFO, "OCC: No console GOP found for burst mode - %r\n", Status)); return Status; } Status = WriteCombineGop (Gop, TRUE); if (EFI_ERROR (Status)) { DEBUG (( (Status == EFI_ALREADY_STARTED) ? DEBUG_INFO : DEBUG_WARN, "OCC: Failed to set burst mode - %r\n", Status )); } return Status; }