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