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