1 /*
2  * PROJECT:     ReactOS Xbox miniport video driver
3  * LICENSE:     GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later)
4  * PURPOSE:     Simple framebuffer driver for NVIDIA NV2A XGPU
5  * COPYRIGHT:   Copyright 2004 Gé van Geldorp
6  *              Copyright 2004 Filip Navara
7  *              Copyright 2019-2020 Stanislav Motylkov (x86corez@gmail.com)
8  *
9  * TODO:
10  * - Check input parameters everywhere.
11  * - Call VideoPortVerifyAccessRanges to reserve the memory we're about
12  *   to map.
13  */
14 
15 /* INCLUDES *******************************************************************/
16 
17 #include "xboxvmp.h"
18 
19 #include <debug.h>
20 #include <dpfilter.h>
21 
22 #include <drivers/xbox/xgpu.h>
23 
24 /* PUBLIC AND PRIVATE FUNCTIONS ***********************************************/
25 
26 ULONG
27 NTAPI
28 DriverEntry(
29     IN PVOID Context1,
30     IN PVOID Context2)
31 {
32     VIDEO_HW_INITIALIZATION_DATA InitData;
33 
34     VideoPortZeroMemory(&InitData, sizeof(InitData));
35     InitData.AdapterInterfaceType = PCIBus;
36     InitData.HwInitDataSize = sizeof(VIDEO_HW_INITIALIZATION_DATA);
37     InitData.HwFindAdapter = XboxVmpFindAdapter;
38     InitData.HwInitialize = XboxVmpInitialize;
39     InitData.HwStartIO = XboxVmpStartIO;
40     InitData.HwResetHw = XboxVmpResetHw;
41     InitData.HwGetPowerState = XboxVmpGetPowerState;
42     InitData.HwSetPowerState = XboxVmpSetPowerState;
43     InitData.HwDeviceExtensionSize = sizeof(XBOXVMP_DEVICE_EXTENSION);
44 
45     return VideoPortInitialize(Context1, Context2, &InitData, NULL);
46 }
47 
48 /*
49  * XboxVmpFindAdapter
50  *
51  * Detects the Xbox Nvidia display adapter.
52  */
53 
54 VP_STATUS
55 NTAPI
56 XboxVmpFindAdapter(
57     IN PVOID HwDeviceExtension,
58     IN PVOID HwContext,
59     IN PWSTR ArgumentString,
60     IN OUT PVIDEO_PORT_CONFIG_INFO ConfigInfo,
61     OUT PUCHAR Again)
62 {
63     PXBOXVMP_DEVICE_EXTENSION XboxVmpDeviceExtension;
64     VP_STATUS Status;
65     /* 3 access ranges: for MMIO, VRAM, and Indirect memory access IO ports */
66     VIDEO_ACCESS_RANGE AccessRanges[3];
67     USHORT VendorId = 0x10DE; /* NVIDIA Corporation */
68     USHORT DeviceId = 0x02A0; /* NV2A XGPU */
69     ULONG Slot = 0;
70 
71     TRACE_(IHVVIDEO, "XboxVmpFindAdapter\n");
72 
73     XboxVmpDeviceExtension = (PXBOXVMP_DEVICE_EXTENSION)HwDeviceExtension;
74 
75     VideoPortZeroMemory(&AccessRanges, sizeof(AccessRanges));
76     Status = VideoPortGetAccessRanges(HwDeviceExtension, 0, NULL,
77                                       RTL_NUMBER_OF(AccessRanges), AccessRanges,
78                                       &VendorId, &DeviceId, &Slot);
79     if (Status == NO_ERROR)
80     {
81         XboxVmpDeviceExtension->PhysControlStart = AccessRanges[0].RangeStart;
82         XboxVmpDeviceExtension->ControlLength = AccessRanges[0].RangeLength;
83         XboxVmpDeviceExtension->PhysFrameBufferStart = AccessRanges[1].RangeStart;
84     }
85 
86     return Status;
87 }
88 
89 /*
90  * XboxVmpInitialize
91  *
92  * Performs the first initialization of the adapter, after the HAL has given
93  * up control of the video hardware to the video port driver.
94  */
95 
96 BOOLEAN
97 NTAPI
98 XboxVmpInitialize(
99     PVOID HwDeviceExtension)
100 {
101     PXBOXVMP_DEVICE_EXTENSION XboxVmpDeviceExtension;
102     ULONG inIoSpace = VIDEO_MEMORY_SPACE_MEMORY;
103     ULONG Length;
104 
105     TRACE_(IHVVIDEO, "XboxVmpInitialize\n");
106 
107     XboxVmpDeviceExtension = (PXBOXVMP_DEVICE_EXTENSION)HwDeviceExtension;
108 
109     Length = XboxVmpDeviceExtension->ControlLength;
110     XboxVmpDeviceExtension->VirtControlStart = NULL;
111 
112     if (VideoPortMapMemory(HwDeviceExtension,
113                            XboxVmpDeviceExtension->PhysControlStart,
114                            &Length,
115                            &inIoSpace,
116                            &XboxVmpDeviceExtension->VirtControlStart) != NO_ERROR)
117     {
118         ERR_(IHVVIDEO, "Failed to map control memory\n");
119         return FALSE;
120     }
121 
122     INFO_(IHVVIDEO, "Mapped 0x%x bytes of control mem at 0x%x to virt addr 0x%x\n",
123         XboxVmpDeviceExtension->ControlLength,
124         XboxVmpDeviceExtension->PhysControlStart.u.LowPart,
125         XboxVmpDeviceExtension->VirtControlStart);
126 
127     return TRUE;
128 }
129 
130 /*
131  * XboxVmpStartIO
132  *
133  * Processes the specified Video Request Packet.
134  */
135 
136 BOOLEAN
137 NTAPI
138 XboxVmpStartIO(
139     PVOID HwDeviceExtension,
140     PVIDEO_REQUEST_PACKET RequestPacket)
141 {
142     BOOLEAN Result;
143 
144     RequestPacket->StatusBlock->Status = ERROR_INVALID_PARAMETER;
145 
146     switch (RequestPacket->IoControlCode)
147     {
148         case IOCTL_VIDEO_SET_CURRENT_MODE:
149         {
150             TRACE_(IHVVIDEO, "XboxVmpStartIO IOCTL_VIDEO_SET_CURRENT_MODE\n");
151 
152             if (RequestPacket->InputBufferLength < sizeof(VIDEO_MODE))
153             {
154                 RequestPacket->StatusBlock->Status = ERROR_INSUFFICIENT_BUFFER;
155                 return TRUE;
156             }
157 
158             Result = XboxVmpSetCurrentMode(
159                 (PXBOXVMP_DEVICE_EXTENSION)HwDeviceExtension,
160                 (PVIDEO_MODE)RequestPacket->InputBuffer,
161                 RequestPacket->StatusBlock);
162             break;
163         }
164 
165         case IOCTL_VIDEO_RESET_DEVICE:
166         {
167             TRACE_(IHVVIDEO, "XboxVmpStartIO IOCTL_VIDEO_RESET_DEVICE\n");
168 
169             Result = XboxVmpResetDevice(
170                 (PXBOXVMP_DEVICE_EXTENSION)HwDeviceExtension,
171                 RequestPacket->StatusBlock);
172             break;
173         }
174 
175         case IOCTL_VIDEO_MAP_VIDEO_MEMORY:
176         {
177             TRACE_(IHVVIDEO, "XboxVmpStartIO IOCTL_VIDEO_MAP_VIDEO_MEMORY\n");
178 
179             if (RequestPacket->OutputBufferLength < sizeof(VIDEO_MEMORY_INFORMATION) ||
180                 RequestPacket->InputBufferLength < sizeof(VIDEO_MEMORY))
181             {
182                 RequestPacket->StatusBlock->Status = ERROR_INSUFFICIENT_BUFFER;
183                 return TRUE;
184             }
185 
186             Result = XboxVmpMapVideoMemory(
187                 (PXBOXVMP_DEVICE_EXTENSION)HwDeviceExtension,
188                 (PVIDEO_MEMORY)RequestPacket->InputBuffer,
189                 (PVIDEO_MEMORY_INFORMATION)RequestPacket->OutputBuffer,
190                 RequestPacket->StatusBlock);
191             break;
192         }
193 
194         case IOCTL_VIDEO_UNMAP_VIDEO_MEMORY:
195         {
196             TRACE_(IHVVIDEO, "XboxVmpStartIO IOCTL_VIDEO_UNMAP_VIDEO_MEMORY\n");
197 
198             if (RequestPacket->InputBufferLength < sizeof(VIDEO_MEMORY))
199             {
200                 RequestPacket->StatusBlock->Status = ERROR_INSUFFICIENT_BUFFER;
201                 return TRUE;
202             }
203 
204             Result = XboxVmpUnmapVideoMemory(
205                 (PXBOXVMP_DEVICE_EXTENSION)HwDeviceExtension,
206                 (PVIDEO_MEMORY)RequestPacket->InputBuffer,
207                 RequestPacket->StatusBlock);
208             break;
209         }
210 
211         case IOCTL_VIDEO_QUERY_NUM_AVAIL_MODES:
212         {
213             TRACE_(IHVVIDEO, "XboxVmpStartIO IOCTL_VIDEO_QUERY_NUM_AVAIL_MODES\n");
214 
215             if (RequestPacket->OutputBufferLength < sizeof(VIDEO_NUM_MODES))
216             {
217                 RequestPacket->StatusBlock->Status = ERROR_INSUFFICIENT_BUFFER;
218                 return TRUE;
219             }
220 
221             Result = XboxVmpQueryNumAvailModes(
222                 (PXBOXVMP_DEVICE_EXTENSION)HwDeviceExtension,
223                 (PVIDEO_NUM_MODES)RequestPacket->OutputBuffer,
224                 RequestPacket->StatusBlock);
225             break;
226         }
227 
228         case IOCTL_VIDEO_QUERY_AVAIL_MODES:
229         {
230             TRACE_(IHVVIDEO, "XboxVmpStartIO IOCTL_VIDEO_QUERY_AVAIL_MODES\n");
231 
232             if (RequestPacket->OutputBufferLength < sizeof(VIDEO_MODE_INFORMATION))
233             {
234                 RequestPacket->StatusBlock->Status = ERROR_INSUFFICIENT_BUFFER;
235                 return TRUE;
236             }
237 
238             Result = XboxVmpQueryAvailModes(
239                 (PXBOXVMP_DEVICE_EXTENSION)HwDeviceExtension,
240                 (PVIDEO_MODE_INFORMATION)RequestPacket->OutputBuffer,
241                 RequestPacket->StatusBlock);
242             break;
243         }
244 
245         case IOCTL_VIDEO_QUERY_CURRENT_MODE:
246         {
247             TRACE_(IHVVIDEO, "XboxVmpStartIO IOCTL_VIDEO_QUERY_CURRENT_MODE\n");
248 
249             if (RequestPacket->OutputBufferLength < sizeof(VIDEO_MODE_INFORMATION))
250             {
251                 RequestPacket->StatusBlock->Status = ERROR_INSUFFICIENT_BUFFER;
252                 return TRUE;
253             }
254 
255             Result = XboxVmpQueryCurrentMode(
256                 (PXBOXVMP_DEVICE_EXTENSION)HwDeviceExtension,
257                 (PVIDEO_MODE_INFORMATION)RequestPacket->OutputBuffer,
258                 RequestPacket->StatusBlock);
259             break;
260         }
261 
262         default:
263         {
264             WARN_(IHVVIDEO, "XboxVmpStartIO 0x%x not implemented\n", RequestPacket->IoControlCode);
265 
266             RequestPacket->StatusBlock->Status = ERROR_INVALID_FUNCTION;
267             return FALSE;
268         }
269     }
270 
271     if (Result)
272     {
273         RequestPacket->StatusBlock->Status = NO_ERROR;
274     }
275 
276     return TRUE;
277 }
278 
279 /*
280  * XboxVmpResetHw
281  *
282  * This function is called to reset the hardware to a known state.
283  */
284 
285 BOOLEAN
286 NTAPI
287 XboxVmpResetHw(
288     PVOID DeviceExtension,
289     ULONG Columns,
290     ULONG Rows)
291 {
292     TRACE_(IHVVIDEO, "XboxVmpResetHw\n");
293 
294     if (!XboxVmpResetDevice((PXBOXVMP_DEVICE_EXTENSION)DeviceExtension, NULL))
295     {
296         return FALSE;
297     }
298 
299     return TRUE;
300 }
301 
302 /*
303  * XboxVmpGetPowerState
304  *
305  * Queries whether the device can support the requested power state.
306  */
307 
308 VP_STATUS
309 NTAPI
310 XboxVmpGetPowerState(
311     PVOID HwDeviceExtension,
312     ULONG HwId,
313     PVIDEO_POWER_MANAGEMENT VideoPowerControl)
314 {
315     ERR_(IHVVIDEO, "XboxVmpGetPowerState is not supported\n");
316 
317     return ERROR_INVALID_FUNCTION;
318 }
319 
320 /*
321  * XboxVmpSetPowerState
322  *
323  * Sets the power state of the specified device
324  */
325 
326 VP_STATUS
327 NTAPI
328 XboxVmpSetPowerState(
329     PVOID HwDeviceExtension,
330     ULONG HwId,
331     PVIDEO_POWER_MANAGEMENT VideoPowerControl)
332 {
333     ERR_(IHVVIDEO, "XboxVmpSetPowerState not supported\n");
334 
335     return ERROR_INVALID_FUNCTION;
336 }
337 
338 /*
339  * VBESetCurrentMode
340  *
341  * Sets the adapter to the specified operating mode.
342  */
343 
344 BOOLEAN
345 FASTCALL
346 XboxVmpSetCurrentMode(
347     PXBOXVMP_DEVICE_EXTENSION DeviceExtension,
348     PVIDEO_MODE RequestedMode,
349     PSTATUS_BLOCK StatusBlock)
350 {
351     if (RequestedMode->RequestedMode != 0)
352     {
353         return FALSE;
354     }
355 
356     /* Nothing to do, really. We only support a single mode and we're already
357      * in that mode
358      */
359     return TRUE;
360 }
361 
362 /*
363  * XboxVmpResetDevice
364  *
365  * Resets the video hardware to the default mode, to which it was initialized
366  * at system boot.
367  */
368 
369 BOOLEAN
370 FASTCALL
371 XboxVmpResetDevice(
372     PXBOXVMP_DEVICE_EXTENSION DeviceExtension,
373     PSTATUS_BLOCK StatusBlock)
374 {
375     /* There is nothing to be done here */
376 
377     return TRUE;
378 }
379 
380 /*
381  * XboxVmpMapVideoMemory
382  *
383  * Maps the video hardware frame buffer and video RAM into the virtual address
384  * space of the requestor.
385  */
386 
387 BOOLEAN
388 FASTCALL
389 XboxVmpMapVideoMemory(
390     PXBOXVMP_DEVICE_EXTENSION DeviceExtension,
391     PVIDEO_MEMORY RequestedAddress,
392     PVIDEO_MEMORY_INFORMATION MapInformation,
393     PSTATUS_BLOCK StatusBlock)
394 {
395     PHYSICAL_ADDRESS FrameBuffer;
396     ULONG inIoSpace = VIDEO_MEMORY_SPACE_MEMORY;
397 
398     StatusBlock->Information = sizeof(VIDEO_MEMORY_INFORMATION);
399 
400     /* Reuse framebuffer that was set up by firmware */
401     FrameBuffer.QuadPart = READ_REGISTER_ULONG((ULONG_PTR)DeviceExtension->VirtControlStart + NV2A_CRTC_FRAMEBUFFER_START);
402     /* Framebuffer address offset value is coming from the GPU within
403      * memory mapped I/O address space, so we're comparing only low
404      * 28 bits of the address within actual RAM address space */
405     FrameBuffer.QuadPart &= 0x0FFFFFFF;
406     if (FrameBuffer.QuadPart != 0x3C00000 && FrameBuffer.QuadPart != 0x7C00000)
407     {
408         /* Check framebuffer address (high 4 MB of either 64 or 128 MB RAM) */
409         WARN_(IHVVIDEO, "Non-standard framebuffer address 0x%p\n", FrameBuffer.QuadPart);
410     }
411     /* Verify that framebuffer address is page-aligned */
412     ASSERT(FrameBuffer.QuadPart % PAGE_SIZE == 0);
413 
414     /* Return the address back to GPU memory mapped I/O */
415     FrameBuffer.QuadPart += DeviceExtension->PhysFrameBufferStart.QuadPart;
416     MapInformation->VideoRamBase = RequestedAddress->RequestedVirtualAddress;
417     /* FIXME: obtain fb size from firmware somehow (Cromwell reserves high 4 MB of RAM) */
418     MapInformation->VideoRamLength = NV2A_VIDEO_MEMORY_SIZE;
419 
420     VideoPortMapMemory(
421         DeviceExtension,
422         FrameBuffer,
423         &MapInformation->VideoRamLength,
424         &inIoSpace,
425         &MapInformation->VideoRamBase);
426 
427     MapInformation->FrameBufferBase = MapInformation->VideoRamBase;
428     MapInformation->FrameBufferLength = MapInformation->VideoRamLength;
429 
430     /* Tell the nVidia controller about the framebuffer */
431     WRITE_REGISTER_ULONG((ULONG_PTR)DeviceExtension->VirtControlStart + NV2A_CRTC_FRAMEBUFFER_START, FrameBuffer.u.LowPart);
432 
433     INFO_(IHVVIDEO, "Mapped 0x%x bytes of phys mem at 0x%lx to virt addr 0x%p\n",
434         MapInformation->VideoRamLength, FrameBuffer.u.LowPart, MapInformation->VideoRamBase);
435 
436     return TRUE;
437 }
438 
439 /*
440  * VBEUnmapVideoMemory
441  *
442  * Releases a mapping between the virtual address space and the adapter's
443  * frame buffer and video RAM.
444  */
445 
446 BOOLEAN
447 FASTCALL
448 XboxVmpUnmapVideoMemory(
449     PXBOXVMP_DEVICE_EXTENSION DeviceExtension,
450     PVIDEO_MEMORY VideoMemory,
451     PSTATUS_BLOCK StatusBlock)
452 {
453     VideoPortUnmapMemory(
454         DeviceExtension,
455         VideoMemory->RequestedVirtualAddress,
456         NULL);
457 
458     return TRUE;
459 }
460 
461 /*
462  * XboxVmpQueryNumAvailModes
463  *
464  * Returns the number of video modes supported by the adapter and the size
465  * in bytes of the video mode information, which can be used to allocate a
466  * buffer for an IOCTL_VIDEO_QUERY_AVAIL_MODES request.
467  */
468 
469 BOOLEAN
470 FASTCALL
471 XboxVmpQueryNumAvailModes(
472     PXBOXVMP_DEVICE_EXTENSION DeviceExtension,
473     PVIDEO_NUM_MODES Modes,
474     PSTATUS_BLOCK StatusBlock)
475 {
476     Modes->NumModes = 1;
477     Modes->ModeInformationLength = sizeof(VIDEO_MODE_INFORMATION);
478     StatusBlock->Information = sizeof(VIDEO_NUM_MODES);
479     return TRUE;
480 }
481 
482 /*
483  * XboxVmpQueryAvailModes
484  *
485  * Returns information about each video mode supported by the adapter.
486  */
487 
488 BOOLEAN
489 FASTCALL
490 XboxVmpQueryAvailModes(
491     PXBOXVMP_DEVICE_EXTENSION DeviceExtension,
492     PVIDEO_MODE_INFORMATION VideoMode,
493     PSTATUS_BLOCK StatusBlock)
494 {
495     return XboxVmpQueryCurrentMode(DeviceExtension, VideoMode, StatusBlock);
496 }
497 
498 UCHAR
499 NvGetCrtc(
500     PXBOXVMP_DEVICE_EXTENSION DeviceExtension,
501     UCHAR Index)
502 {
503     WRITE_REGISTER_UCHAR((ULONG_PTR)DeviceExtension->VirtControlStart + NV2A_CRTC_REGISTER_INDEX, Index);
504     return READ_REGISTER_UCHAR((ULONG_PTR)DeviceExtension->VirtControlStart + NV2A_CRTC_REGISTER_VALUE);
505 }
506 
507 UCHAR
508 NvGetBytesPerPixel(
509     PXBOXVMP_DEVICE_EXTENSION DeviceExtension,
510     ULONG ScreenWidth)
511 {
512     UCHAR BytesPerPixel;
513 
514     /* Get BPP directly from NV2A CRTC (magic constants are from Cromwell) */
515     BytesPerPixel = 8 * (((NvGetCrtc(DeviceExtension, 0x19) & 0xE0) << 3) | (NvGetCrtc(DeviceExtension, 0x13) & 0xFF)) / ScreenWidth;
516 
517     if (BytesPerPixel == 4)
518     {
519         ASSERT((NvGetCrtc(DeviceExtension, 0x28) & 0xF) == BytesPerPixel - 1);
520     }
521     else
522     {
523         ASSERT((NvGetCrtc(DeviceExtension, 0x28) & 0xF) == BytesPerPixel);
524     }
525 
526     return BytesPerPixel;
527 }
528 
529 /*
530  * VBEQueryCurrentMode
531  *
532  * Returns information about current video mode.
533  */
534 
535 BOOLEAN
536 FASTCALL
537 XboxVmpQueryCurrentMode(
538     PXBOXVMP_DEVICE_EXTENSION DeviceExtension,
539     PVIDEO_MODE_INFORMATION VideoMode,
540     PSTATUS_BLOCK StatusBlock)
541 {
542     UCHAR BytesPerPixel;
543 
544     VideoMode->Length = sizeof(VIDEO_MODE_INFORMATION);
545     VideoMode->ModeIndex = 0;
546 
547     VideoMode->VisScreenWidth = READ_REGISTER_ULONG((ULONG_PTR)DeviceExtension->VirtControlStart + NV2A_RAMDAC_FP_HVALID_END) + 1;
548     VideoMode->VisScreenHeight = READ_REGISTER_ULONG((ULONG_PTR)DeviceExtension->VirtControlStart + NV2A_RAMDAC_FP_VVALID_END) + 1;
549 
550     if (VideoMode->VisScreenWidth <= 1 || VideoMode->VisScreenHeight <= 1)
551     {
552         ERR_(IHVVIDEO, "Cannot obtain current screen resolution!\n");
553         return FALSE;
554     }
555 
556     BytesPerPixel = NvGetBytesPerPixel(DeviceExtension, VideoMode->VisScreenWidth);
557     ASSERT(BytesPerPixel >= 1 && BytesPerPixel <= 4);
558 
559     VideoMode->ScreenStride = VideoMode->VisScreenWidth * BytesPerPixel;
560     VideoMode->NumberOfPlanes = 1;
561     VideoMode->BitsPerPlane = BytesPerPixel * 8;
562     VideoMode->Frequency = 1;
563     VideoMode->XMillimeter = 0; /* FIXME */
564     VideoMode->YMillimeter = 0; /* FIXME */
565     if (BytesPerPixel >= 3)
566     {
567         VideoMode->NumberRedBits = 8;
568         VideoMode->NumberGreenBits = 8;
569         VideoMode->NumberBlueBits = 8;
570         VideoMode->RedMask = 0xFF0000;
571         VideoMode->GreenMask = 0x00FF00;
572         VideoMode->BlueMask = 0x0000FF;
573     }
574     else
575     {
576         /* FIXME: not implemented */
577         WARN_(IHVVIDEO, "BytesPerPixel %d - not implemented\n", BytesPerPixel);
578     }
579     VideoMode->VideoMemoryBitmapWidth = VideoMode->VisScreenWidth;
580     VideoMode->VideoMemoryBitmapHeight = VideoMode->VisScreenHeight;
581     VideoMode->AttributeFlags = VIDEO_MODE_GRAPHICS | VIDEO_MODE_COLOR |
582         VIDEO_MODE_NO_OFF_SCREEN;
583     VideoMode->DriverSpecificAttributeFlags = 0;
584 
585     StatusBlock->Information = sizeof(VIDEO_MODE_INFORMATION);
586 
587     /* Verify that screen fits framebuffer size */
588     if (VideoMode->VisScreenWidth * VideoMode->VisScreenHeight * (VideoMode->BitsPerPlane / 8) > NV2A_VIDEO_MEMORY_SIZE)
589     {
590         ERR_(IHVVIDEO, "Current screen resolution exceeds video memory bounds!\n");
591         return FALSE;
592     }
593 
594     return TRUE;
595 }
596 
597 /* EOF */
598