/* * PROJECT: ReactOS Boot Video Driver for Original Xbox * LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later) * PURPOSE: Main file * COPYRIGHT: Copyright 2004 Gé van Geldorp (gvg@reactos.org) * Copyright 2005 Filip Navara (navaraf@reactos.org) * Copyright 2020 Stanislav Motylkov (x86corez@gmail.com) */ #include "precomp.h" #include #include /* GLOBALS ********************************************************************/ static ULONG_PTR FrameBufferStart = 0; static ULONG FrameBufferWidth, FrameBufferHeight, PanH, PanV; static UCHAR BytesPerPixel; static RGBQUAD CachedPalette[BV_MAX_COLORS]; static PUCHAR BackBuffer = NULL; /* PRIVATE FUNCTIONS *********************************************************/ static UCHAR NvGetCrtc( ULONG Base, UCHAR Index) { WRITE_REGISTER_UCHAR((PUCHAR)(Base + NV2A_CRTC_REGISTER_INDEX), Index); return READ_REGISTER_UCHAR((PUCHAR)(Base + NV2A_CRTC_REGISTER_VALUE)); } static UCHAR NvGetBytesPerPixel( ULONG Base, ULONG ScreenWidth) { /* Get BPP directly from NV2A CRTC (magic constants are from Cromwell) */ UCHAR BytesPerPixel = 8 * (((NvGetCrtc(Base, 0x19) & 0xE0) << 3) | (NvGetCrtc(Base, 0x13) & 0xFF)) / ScreenWidth; if (BytesPerPixel == 4) { ASSERT((NvGetCrtc(Base, 0x28) & 0xF) == BytesPerPixel - 1); } else { ASSERT((NvGetCrtc(Base, 0x28) & 0xF) == BytesPerPixel); } return BytesPerPixel; } static VOID ApplyPalette(VOID) { PULONG Frame = (PULONG)FrameBufferStart; ULONG x, y; /* Top panning */ for (x = 0; x < PanV * FrameBufferWidth; x++) { *Frame++ = CachedPalette[0]; } /* Left panning */ for (y = 0; y < SCREEN_HEIGHT; y++) { Frame = (PULONG)(FrameBufferStart + FB_OFFSET(-PanH, y)); for (x = 0; x < PanH; x++) { *Frame++ = CachedPalette[0]; } } /* Screen redraw */ PUCHAR Back = BackBuffer; for (y = 0; y < SCREEN_HEIGHT; y++) { Frame = (PULONG)(FrameBufferStart + FB_OFFSET(0, y)); for (x = 0; x < SCREEN_WIDTH; x++) { *Frame++ = CachedPalette[*Back++]; } } /* Right panning */ for (y = 0; y < SCREEN_HEIGHT; y++) { Frame = (PULONG)(FrameBufferStart + FB_OFFSET(SCREEN_WIDTH, y)); for (x = 0; x < PanH; x++) { *Frame++ = CachedPalette[0]; } } /* Bottom panning */ Frame = (PULONG)(FrameBufferStart + FB_OFFSET(-PanH, SCREEN_HEIGHT)); for (x = 0; x < PanV * FrameBufferWidth; x++) { *Frame++ = CachedPalette[0]; } } /* PUBLIC FUNCTIONS **********************************************************/ BOOLEAN NTAPI VidInitialize( _In_ BOOLEAN SetMode) { BOOLEAN Result = FALSE; /* FIXME: Add platform check */ /* 1. Access PCI device 1:0:0 */ /* 2. Check if device ID is 10DE:02A0 */ /* FIXME: Get device MMIO ranges from PCI */ PHYSICAL_ADDRESS PhysControlStart = {.QuadPart = 0xFD000000}; PHYSICAL_ADDRESS PhysFrameBufferStart = {.QuadPart = 0xF0000000}; ULONG ControlLength = 16 * 1024 * 1024; ULONG_PTR ControlStart = (ULONG_PTR)MmMapIoSpace(PhysControlStart, ControlLength, MmNonCached); if (!ControlStart) { DPRINT1("Out of memory!\n"); return FALSE; } ULONG_PTR FrameBuffer = READ_REGISTER_ULONG((PULONG)(ControlStart + NV2A_CRTC_FRAMEBUFFER_START)); FrameBufferWidth = READ_REGISTER_ULONG((PULONG)(ControlStart + NV2A_RAMDAC_FP_HVALID_END)) + 1; FrameBufferHeight = READ_REGISTER_ULONG((PULONG)(ControlStart + NV2A_RAMDAC_FP_VVALID_END)) + 1; FrameBuffer &= 0x0FFFFFFF; if (FrameBuffer != 0x3C00000 && FrameBuffer != 0x7C00000) { /* Check framebuffer address (high 4 MB of either 64 or 128 MB RAM) */ DPRINT1("Non-standard framebuffer address 0x%p\n", FrameBuffer); } /* Verify that framebuffer address is page-aligned */ ASSERT(FrameBuffer % PAGE_SIZE == 0); if (FrameBufferWidth < SCREEN_WIDTH || FrameBufferHeight < SCREEN_HEIGHT) { DPRINT1("Unsupported screen resolution!\n"); goto cleanup; } BytesPerPixel = NvGetBytesPerPixel(ControlStart, FrameBufferWidth); ASSERT(BytesPerPixel >= 1 && BytesPerPixel <= 4); if (BytesPerPixel != 4) { DPRINT1("Unsupported BytesPerPixel = %d\n", BytesPerPixel); goto cleanup; } /* Calculate panning values */ PanH = (FrameBufferWidth - SCREEN_WIDTH) / 2; PanV = (FrameBufferHeight - SCREEN_HEIGHT) / 2; /* Verify that screen fits framebuffer size */ ULONG FrameBufferSize = FrameBufferWidth * FrameBufferHeight * BytesPerPixel; /* FIXME: obtain fb size from firmware somehow (Cromwell reserves high 4 MB of RAM) */ if (FrameBufferSize > NV2A_VIDEO_MEMORY_SIZE) { DPRINT1("Current screen resolution exceeds video memory bounds!\n"); goto cleanup; } /* * Reserve off-screen area for the backbuffer that contains 8-bit indexed * color screen image, plus preserved row data. */ ULONG BackBufferSize = SCREEN_WIDTH * (SCREEN_HEIGHT + BOOTCHAR_HEIGHT + 1); /* Make sure there is enough video memory for backbuffer */ if (NV2A_VIDEO_MEMORY_SIZE - FrameBufferSize < BackBufferSize) { DPRINT1("Out of memory!\n"); goto cleanup; } /* Return the address back to GPU memory mapped I/O */ PhysFrameBufferStart.QuadPart += FrameBuffer; FrameBufferStart = (ULONG_PTR)MmMapIoSpace(PhysFrameBufferStart, NV2A_VIDEO_MEMORY_SIZE, MmNonCached); if (!FrameBufferStart) { DPRINT1("Out of memory!\n"); goto cleanup; } Result = TRUE; /* Place backbuffer in the hidden part of framebuffer */ BackBuffer = (PUCHAR)(FrameBufferStart + NV2A_VIDEO_MEMORY_SIZE - BackBufferSize); /* Now check if we have to set the mode */ if (SetMode) VidResetDisplay(TRUE); cleanup: if (ControlStart) MmUnmapIoSpace((PVOID)ControlStart, ControlLength); /* Video is ready */ return Result; } VOID NTAPI VidCleanUp(VOID) { /* Just fill the screen black */ VidSolidColorFill(0, 0, SCREEN_WIDTH - 1, SCREEN_HEIGHT - 1, BV_COLOR_BLACK); } VOID NTAPI VidResetDisplay( _In_ BOOLEAN HalReset) { /* Clear the current position */ VidpCurrentX = 0; VidpCurrentY = 0; /* Clear the screen with HAL if we were asked to */ if (HalReset) HalResetDisplay(); /* Re-initialize the palette and fill the screen black */ RtlZeroMemory((PULONG)FrameBufferStart, NV2A_VIDEO_MEMORY_SIZE); InitializePalette(); VidSolidColorFill(0, 0, SCREEN_WIDTH - 1, SCREEN_HEIGHT - 1, BV_COLOR_BLACK); } VOID NTAPI InitPaletteWithTable( _In_ PULONG Table, _In_ ULONG Count) { PULONG Entry = Table; for (ULONG i = 0; i < Count; i++, Entry++) { CachedPalette[i] = *Entry | 0xFF000000; } ApplyPalette(); } VOID PrepareForSetPixel(VOID) { /* Nothing to prepare */ NOTHING; } VOID SetPixel( _In_ ULONG Left, _In_ ULONG Top, _In_ UCHAR Color) { PUCHAR Back = BackBuffer + BB_OFFSET(Left, Top); PULONG Frame = (PULONG)(FrameBufferStart + FB_OFFSET(Left, Top)); *Back = Color; *Frame = CachedPalette[Color]; } VOID NTAPI PreserveRow( _In_ ULONG CurrentTop, _In_ ULONG TopDelta, _In_ BOOLEAN Restore) { PUCHAR NewPosition, OldPosition; /* Calculate the position in memory for the row */ if (Restore) { /* Restore the row by copying back the contents saved off-screen */ NewPosition = BackBuffer + BB_OFFSET(0, CurrentTop); OldPosition = BackBuffer + BB_OFFSET(0, SCREEN_HEIGHT); } else { /* Preserve the row by saving its contents off-screen */ NewPosition = BackBuffer + BB_OFFSET(0, SCREEN_HEIGHT); OldPosition = BackBuffer + BB_OFFSET(0, CurrentTop); } /* Set the count and loop every pixel of backbuffer */ ULONG Count = TopDelta * SCREEN_WIDTH; RtlCopyMemory(NewPosition, OldPosition, Count); if (Restore) { NewPosition = BackBuffer + BB_OFFSET(0, CurrentTop); /* Set the count and loop every pixel of framebuffer */ for (ULONG y = 0; y < TopDelta; y++) { PULONG Frame = (PULONG)(FrameBufferStart + FB_OFFSET(0, CurrentTop + y)); Count = SCREEN_WIDTH; while (Count--) { *Frame++ = CachedPalette[*NewPosition++]; } } } } VOID NTAPI DoScroll( _In_ ULONG Scroll) { ULONG RowSize = VidpScrollRegion[2] - VidpScrollRegion[0] + 1; /* Calculate the position in memory for the row */ PUCHAR OldPosition = BackBuffer + BB_OFFSET(VidpScrollRegion[0], VidpScrollRegion[1] + Scroll); PUCHAR NewPosition = BackBuffer + BB_OFFSET(VidpScrollRegion[0], VidpScrollRegion[1]); /* Start loop */ for (ULONG Top = VidpScrollRegion[1]; Top <= VidpScrollRegion[3]; ++Top) { ULONG i; /* Scroll the row */ RtlCopyMemory(NewPosition, OldPosition, RowSize); PULONG Frame = (PULONG)(FrameBufferStart + FB_OFFSET(VidpScrollRegion[0], Top)); for (i = 0; i < RowSize; ++i) Frame[i] = CachedPalette[NewPosition[i]]; OldPosition += SCREEN_WIDTH; NewPosition += SCREEN_WIDTH; } } VOID NTAPI DisplayCharacter( _In_ CHAR Character, _In_ ULONG Left, _In_ ULONG Top, _In_ ULONG TextColor, _In_ ULONG BackColor) { /* Get the font and pixel pointer */ PUCHAR FontChar = GetFontPtr(Character); /* Loop each pixel height */ for (ULONG y = Top; y < Top + BOOTCHAR_HEIGHT; y++, FontChar += FONT_PTR_DELTA) { /* Loop each pixel width */ ULONG x = Left; for (UCHAR bit = 1 << (BOOTCHAR_WIDTH - 1); bit > 0; bit >>= 1, x++) { /* Check if we should draw this pixel */ if (*FontChar & bit) { /* We do, use the given Text Color */ SetPixel(x, y, (UCHAR)TextColor); } else if (BackColor < BV_COLOR_NONE) { /* * This is a background pixel. We're drawing it * unless it's transparent. */ SetPixel(x, y, (UCHAR)BackColor); } } } } VOID NTAPI VidSolidColorFill( _In_ ULONG Left, _In_ ULONG Top, _In_ ULONG Right, _In_ ULONG Bottom, _In_ UCHAR Color) { while (Top <= Bottom) { PUCHAR Back = BackBuffer + BB_OFFSET(Left, Top); PULONG Frame = (PULONG)(FrameBufferStart + FB_OFFSET(Left, Top)); ULONG L = Left; while (L++ <= Right) { *Back++ = Color; *Frame++ = CachedPalette[Color]; } Top++; } } VOID NTAPI VidScreenToBufferBlt( _Out_writes_bytes_(Delta * Height) PUCHAR Buffer, _In_ ULONG Left, _In_ ULONG Top, _In_ ULONG Width, _In_ ULONG Height, _In_ ULONG Delta) { /* Clear the destination buffer */ RtlZeroMemory(Buffer, Delta * Height); /* Start the outer Y height loop */ for (ULONG y = 0; y < Height; y++) { /* Set current scanline */ PUCHAR Back = BackBuffer + BB_OFFSET(Left, Top + y); PUCHAR Buf = Buffer + y * Delta; /* Start the X inner loop */ for (ULONG x = 0; x < Width; x += sizeof(USHORT)) { /* Read the current value */ *Buf = (*Back++ & 0xF) << 4; *Buf |= *Back++ & 0xF; Buf++; } } }