1 /** @file
2   This driver is a implementation of the Graphics Output Protocol
3   for the QEMU ramfb device.
4 
5   Copyright (c) 2018, Red Hat Inc.
6 
7   SPDX-License-Identifier: BSD-2-Clause-Patent
8 
9 **/
10 
11 #include <Protocol/GraphicsOutput.h>
12 
13 #include <Library/BaseLib.h>
14 #include <Library/BaseMemoryLib.h>
15 #include <Library/DebugLib.h>
16 #include <Library/DevicePathLib.h>
17 #include <Library/FrameBufferBltLib.h>
18 #include <Library/MemoryAllocationLib.h>
19 #include <Library/UefiBootServicesTableLib.h>
20 #include <Library/QemuFwCfgLib.h>
21 
22 #include <Guid/QemuRamfb.h>
23 
24 #define RAMFB_FORMAT  0x34325258 /* DRM_FORMAT_XRGB8888 */
25 #define RAMFB_BPP     4
26 
27 #pragma pack (1)
28 typedef struct RAMFB_CONFIG {
29   UINT64 Address;
30   UINT32 FourCC;
31   UINT32 Flags;
32   UINT32 Width;
33   UINT32 Height;
34   UINT32 Stride;
35 } RAMFB_CONFIG;
36 #pragma pack ()
37 
38 STATIC EFI_HANDLE                    mRamfbHandle;
39 STATIC EFI_HANDLE                    mGopHandle;
40 STATIC FRAME_BUFFER_CONFIGURE        *mQemuRamfbFrameBufferBltConfigure;
41 STATIC UINTN                         mQemuRamfbFrameBufferBltConfigureSize;
42 STATIC FIRMWARE_CONFIG_ITEM          mRamfbFwCfgItem;
43 
44 STATIC EFI_GRAPHICS_OUTPUT_MODE_INFORMATION mQemuRamfbModeInfo[] = {
45   {
46     0,    // Version
47     640,  // HorizontalResolution
48     480,  // VerticalResolution
49   },{
50     0,    // Version
51     800,  // HorizontalResolution
52     600,  // VerticalResolution
53   },{
54     0,    // Version
55     1024, // HorizontalResolution
56     768,  // VerticalResolution
57   }
58 };
59 
60 STATIC EFI_GRAPHICS_OUTPUT_PROTOCOL_MODE mQemuRamfbMode = {
61   ARRAY_SIZE (mQemuRamfbModeInfo),                // MaxMode
62   0,                                              // Mode
63   mQemuRamfbModeInfo,                             // Info
64   sizeof (EFI_GRAPHICS_OUTPUT_MODE_INFORMATION),  // SizeOfInfo
65 };
66 
67 STATIC
68 EFI_STATUS
69 EFIAPI
QemuRamfbGraphicsOutputQueryMode(IN EFI_GRAPHICS_OUTPUT_PROTOCOL * This,IN UINT32 ModeNumber,OUT UINTN * SizeOfInfo,OUT EFI_GRAPHICS_OUTPUT_MODE_INFORMATION ** Info)70 QemuRamfbGraphicsOutputQueryMode (
71   IN  EFI_GRAPHICS_OUTPUT_PROTOCOL          *This,
72   IN  UINT32                                ModeNumber,
73   OUT UINTN                                 *SizeOfInfo,
74   OUT EFI_GRAPHICS_OUTPUT_MODE_INFORMATION  **Info
75   )
76 {
77   EFI_GRAPHICS_OUTPUT_MODE_INFORMATION  *ModeInfo;
78 
79   if (Info == NULL || SizeOfInfo == NULL ||
80       ModeNumber >= mQemuRamfbMode.MaxMode) {
81     return EFI_INVALID_PARAMETER;
82   }
83   ModeInfo = &mQemuRamfbModeInfo[ModeNumber];
84 
85   *Info = AllocateCopyPool (sizeof (EFI_GRAPHICS_OUTPUT_MODE_INFORMATION),
86             ModeInfo);
87   if (*Info == NULL) {
88     return EFI_OUT_OF_RESOURCES;
89   }
90   *SizeOfInfo = sizeof (EFI_GRAPHICS_OUTPUT_MODE_INFORMATION);
91 
92   return EFI_SUCCESS;
93 }
94 
95 STATIC
96 EFI_STATUS
97 EFIAPI
QemuRamfbGraphicsOutputSetMode(IN EFI_GRAPHICS_OUTPUT_PROTOCOL * This,IN UINT32 ModeNumber)98 QemuRamfbGraphicsOutputSetMode (
99   IN  EFI_GRAPHICS_OUTPUT_PROTOCOL *This,
100   IN  UINT32                       ModeNumber
101   )
102 {
103   EFI_GRAPHICS_OUTPUT_MODE_INFORMATION  *ModeInfo;
104   RAMFB_CONFIG                          Config;
105   EFI_GRAPHICS_OUTPUT_BLT_PIXEL         Black;
106   RETURN_STATUS                         Status;
107 
108   if (ModeNumber >= mQemuRamfbMode.MaxMode) {
109     return EFI_UNSUPPORTED;
110   }
111   ModeInfo = &mQemuRamfbModeInfo[ModeNumber];
112 
113   DEBUG ((DEBUG_INFO, "Ramfb: SetMode %u (%ux%u)\n", ModeNumber,
114     ModeInfo->HorizontalResolution, ModeInfo->VerticalResolution));
115 
116   Config.Address = SwapBytes64 (mQemuRamfbMode.FrameBufferBase);
117   Config.FourCC  = SwapBytes32 (RAMFB_FORMAT);
118   Config.Flags   = SwapBytes32 (0);
119   Config.Width   = SwapBytes32 (ModeInfo->HorizontalResolution);
120   Config.Height  = SwapBytes32 (ModeInfo->VerticalResolution);
121   Config.Stride  = SwapBytes32 (ModeInfo->HorizontalResolution * RAMFB_BPP);
122 
123   Status = FrameBufferBltConfigure (
124              (VOID*)(UINTN)mQemuRamfbMode.FrameBufferBase,
125              ModeInfo,
126              mQemuRamfbFrameBufferBltConfigure,
127              &mQemuRamfbFrameBufferBltConfigureSize
128              );
129 
130   if (Status == RETURN_BUFFER_TOO_SMALL) {
131     if (mQemuRamfbFrameBufferBltConfigure != NULL) {
132       FreePool (mQemuRamfbFrameBufferBltConfigure);
133     }
134     mQemuRamfbFrameBufferBltConfigure =
135       AllocatePool (mQemuRamfbFrameBufferBltConfigureSize);
136     if (mQemuRamfbFrameBufferBltConfigure == NULL) {
137       mQemuRamfbFrameBufferBltConfigureSize = 0;
138       return EFI_OUT_OF_RESOURCES;
139     }
140 
141     Status = FrameBufferBltConfigure (
142                (VOID*)(UINTN)mQemuRamfbMode.FrameBufferBase,
143                ModeInfo,
144                mQemuRamfbFrameBufferBltConfigure,
145                &mQemuRamfbFrameBufferBltConfigureSize
146                );
147   }
148   if (RETURN_ERROR (Status)) {
149     ASSERT (Status == RETURN_UNSUPPORTED);
150     return Status;
151   }
152 
153   mQemuRamfbMode.Mode = ModeNumber;
154   mQemuRamfbMode.Info = ModeInfo;
155 
156   QemuFwCfgSelectItem (mRamfbFwCfgItem);
157   QemuFwCfgWriteBytes (sizeof (Config), &Config);
158 
159   //
160   // clear screen
161   //
162   ZeroMem (&Black, sizeof (Black));
163   Status = FrameBufferBlt (
164              mQemuRamfbFrameBufferBltConfigure,
165              &Black,
166              EfiBltVideoFill,
167              0,                               // SourceX -- ignored
168              0,                               // SourceY -- ignored
169              0,                               // DestinationX
170              0,                               // DestinationY
171              ModeInfo->HorizontalResolution,  // Width
172              ModeInfo->VerticalResolution,    // Height
173              0                                // Delta -- ignored
174              );
175   if (RETURN_ERROR (Status)) {
176     DEBUG ((DEBUG_WARN, "%a: clearing the screen failed: %r\n",
177       __FUNCTION__, Status));
178   }
179 
180   return EFI_SUCCESS;
181 }
182 
183 STATIC
184 EFI_STATUS
185 EFIAPI
QemuRamfbGraphicsOutputBlt(IN EFI_GRAPHICS_OUTPUT_PROTOCOL * This,IN EFI_GRAPHICS_OUTPUT_BLT_PIXEL * BltBuffer,OPTIONAL IN EFI_GRAPHICS_OUTPUT_BLT_OPERATION BltOperation,IN UINTN SourceX,IN UINTN SourceY,IN UINTN DestinationX,IN UINTN DestinationY,IN UINTN Width,IN UINTN Height,IN UINTN Delta)186 QemuRamfbGraphicsOutputBlt (
187   IN  EFI_GRAPHICS_OUTPUT_PROTOCOL          *This,
188   IN  EFI_GRAPHICS_OUTPUT_BLT_PIXEL         *BltBuffer, OPTIONAL
189   IN  EFI_GRAPHICS_OUTPUT_BLT_OPERATION     BltOperation,
190   IN  UINTN                                 SourceX,
191   IN  UINTN                                 SourceY,
192   IN  UINTN                                 DestinationX,
193   IN  UINTN                                 DestinationY,
194   IN  UINTN                                 Width,
195   IN  UINTN                                 Height,
196   IN  UINTN                                 Delta
197   )
198 {
199   return FrameBufferBlt (
200            mQemuRamfbFrameBufferBltConfigure,
201            BltBuffer,
202            BltOperation,
203            SourceX,
204            SourceY,
205            DestinationX,
206            DestinationY,
207            Width,
208            Height,
209            Delta
210            );
211 }
212 
213 STATIC EFI_GRAPHICS_OUTPUT_PROTOCOL mQemuRamfbGraphicsOutput = {
214   QemuRamfbGraphicsOutputQueryMode,
215   QemuRamfbGraphicsOutputSetMode,
216   QemuRamfbGraphicsOutputBlt,
217   &mQemuRamfbMode,
218 };
219 
220 EFI_STATUS
221 EFIAPI
InitializeQemuRamfb(IN EFI_HANDLE ImageHandle,IN EFI_SYSTEM_TABLE * SystemTable)222 InitializeQemuRamfb (
223   IN EFI_HANDLE           ImageHandle,
224   IN EFI_SYSTEM_TABLE     *SystemTable
225   )
226 {
227   EFI_DEVICE_PATH_PROTOCOL  *RamfbDevicePath;
228   EFI_DEVICE_PATH_PROTOCOL  *GopDevicePath;
229   VOID                      *DevicePath;
230   VENDOR_DEVICE_PATH        VendorDeviceNode;
231   ACPI_ADR_DEVICE_PATH      AcpiDeviceNode;
232   EFI_STATUS                Status;
233   EFI_PHYSICAL_ADDRESS      FbBase;
234   UINTN                     FbSize, MaxFbSize, Pages;
235   UINTN                     FwCfgSize;
236   UINTN                     Index;
237 
238   if (!QemuFwCfgIsAvailable ()) {
239     DEBUG ((DEBUG_INFO, "Ramfb: no FwCfg\n"));
240     return EFI_NOT_FOUND;
241   }
242 
243   Status = QemuFwCfgFindFile ("etc/ramfb", &mRamfbFwCfgItem, &FwCfgSize);
244   if (EFI_ERROR (Status)) {
245     return EFI_NOT_FOUND;
246   }
247   if (FwCfgSize != sizeof (RAMFB_CONFIG)) {
248     DEBUG ((DEBUG_ERROR, "Ramfb: FwCfg size mismatch (expected %lu, got %lu)\n",
249       (UINT64)sizeof (RAMFB_CONFIG), (UINT64)FwCfgSize));
250     return EFI_PROTOCOL_ERROR;
251   }
252 
253   MaxFbSize = 0;
254   for (Index = 0; Index < ARRAY_SIZE (mQemuRamfbModeInfo); Index++) {
255     mQemuRamfbModeInfo[Index].PixelsPerScanLine =
256       mQemuRamfbModeInfo[Index].HorizontalResolution;
257     mQemuRamfbModeInfo[Index].PixelFormat =
258       PixelBlueGreenRedReserved8BitPerColor;
259     FbSize = RAMFB_BPP *
260       mQemuRamfbModeInfo[Index].HorizontalResolution *
261       mQemuRamfbModeInfo[Index].VerticalResolution;
262     if (MaxFbSize < FbSize) {
263       MaxFbSize = FbSize;
264     }
265     DEBUG ((DEBUG_INFO, "Ramfb: Mode %lu: %ux%u, %lu kB\n", (UINT64)Index,
266       mQemuRamfbModeInfo[Index].HorizontalResolution,
267       mQemuRamfbModeInfo[Index].VerticalResolution,
268       (UINT64)(FbSize / 1024)));
269   }
270 
271   Pages = EFI_SIZE_TO_PAGES (MaxFbSize);
272   MaxFbSize = EFI_PAGES_TO_SIZE (Pages);
273   FbBase = (EFI_PHYSICAL_ADDRESS)(UINTN)AllocateReservedPages (Pages);
274   if (FbBase == 0) {
275     DEBUG ((DEBUG_ERROR, "Ramfb: memory allocation failed\n"));
276     return EFI_OUT_OF_RESOURCES;
277   }
278   DEBUG ((DEBUG_INFO, "Ramfb: Framebuffer at 0x%lx, %lu kB, %lu pages\n",
279     (UINT64)FbBase, (UINT64)(MaxFbSize / 1024), (UINT64)Pages));
280   mQemuRamfbMode.FrameBufferSize = MaxFbSize;
281   mQemuRamfbMode.FrameBufferBase = FbBase;
282 
283   //
284   // 800 x 600
285   //
286   QemuRamfbGraphicsOutputSetMode (&mQemuRamfbGraphicsOutput, 1);
287 
288   //
289   // ramfb vendor devpath
290   //
291   VendorDeviceNode.Header.Type = HARDWARE_DEVICE_PATH;
292   VendorDeviceNode.Header.SubType = HW_VENDOR_DP;
293   CopyGuid (&VendorDeviceNode.Guid, &gQemuRamfbGuid);
294   SetDevicePathNodeLength (&VendorDeviceNode.Header,
295     sizeof (VENDOR_DEVICE_PATH));
296 
297   RamfbDevicePath = AppendDevicePathNode (NULL,
298     (EFI_DEVICE_PATH_PROTOCOL *) &VendorDeviceNode);
299   if (RamfbDevicePath == NULL) {
300     Status = EFI_OUT_OF_RESOURCES;
301     goto FreeFramebuffer;
302   }
303 
304   Status = gBS->InstallMultipleProtocolInterfaces (
305                   &mRamfbHandle,
306                   &gEfiDevicePathProtocolGuid,
307                   RamfbDevicePath,
308                   NULL
309                   );
310   if (EFI_ERROR (Status)) {
311     DEBUG ((DEBUG_ERROR, "Ramfb: install Ramfb Vendor DevicePath failed: %r\n",
312       Status));
313     goto FreeRamfbDevicePath;
314   }
315 
316   //
317   // gop devpath + protocol
318   //
319   AcpiDeviceNode.Header.Type = ACPI_DEVICE_PATH;
320   AcpiDeviceNode.Header.SubType = ACPI_ADR_DP;
321   AcpiDeviceNode.ADR = ACPI_DISPLAY_ADR (
322     1,                                       // DeviceIdScheme
323     0,                                       // HeadId
324     0,                                       // NonVgaOutput
325     1,                                       // BiosCanDetect
326     0,                                       // VendorInfo
327     ACPI_ADR_DISPLAY_TYPE_EXTERNAL_DIGITAL,  // Type
328     0,                                       // Port
329     0                                        // Index
330     );
331   SetDevicePathNodeLength (&AcpiDeviceNode.Header,
332     sizeof (ACPI_ADR_DEVICE_PATH));
333 
334   GopDevicePath = AppendDevicePathNode (RamfbDevicePath,
335     (EFI_DEVICE_PATH_PROTOCOL *) &AcpiDeviceNode);
336   if (GopDevicePath == NULL) {
337     Status = EFI_OUT_OF_RESOURCES;
338     goto FreeRamfbHandle;
339   }
340 
341   Status = gBS->InstallMultipleProtocolInterfaces (
342                   &mGopHandle,
343                   &gEfiDevicePathProtocolGuid,
344                   GopDevicePath,
345                   &gEfiGraphicsOutputProtocolGuid,
346                   &mQemuRamfbGraphicsOutput,
347                   NULL
348                   );
349   if (EFI_ERROR (Status)) {
350     DEBUG ((DEBUG_ERROR, "Ramfb: install GOP DevicePath failed: %r\n",
351       Status));
352     goto FreeGopDevicePath;
353   }
354 
355   Status = gBS->OpenProtocol (
356                   mRamfbHandle,
357                   &gEfiDevicePathProtocolGuid,
358                   &DevicePath,
359                   gImageHandle,
360                   mGopHandle,
361                   EFI_OPEN_PROTOCOL_BY_CHILD_CONTROLLER
362                   );
363   if (EFI_ERROR (Status)) {
364     DEBUG ((DEBUG_ERROR, "Ramfb: OpenProtocol failed: %r\n", Status));
365     goto FreeGopHandle;
366   }
367 
368   return EFI_SUCCESS;
369 
370 FreeGopHandle:
371   gBS->UninstallMultipleProtocolInterfaces (
372          mGopHandle,
373          &gEfiDevicePathProtocolGuid,
374          GopDevicePath,
375          &gEfiGraphicsOutputProtocolGuid,
376          &mQemuRamfbGraphicsOutput,
377          NULL
378          );
379 FreeGopDevicePath:
380   FreePool (GopDevicePath);
381 FreeRamfbHandle:
382   gBS->UninstallMultipleProtocolInterfaces (
383          mRamfbHandle,
384          &gEfiDevicePathProtocolGuid,
385          RamfbDevicePath,
386          NULL
387          );
388 FreeRamfbDevicePath:
389   FreePool (RamfbDevicePath);
390 FreeFramebuffer:
391   FreePages ((VOID*)(UINTN)mQemuRamfbMode.FrameBufferBase, Pages);
392   return Status;
393 }
394