]> CyberLeo.Net >> Repos - FreeBSD/releng/10.3.git/blob - sys/boot/efi/loader/arch/amd64/framebuffer.c
- Copy stable/10@296371 to releng/10.3 in preparation for 10.3-RC1
[FreeBSD/releng/10.3.git] / sys / boot / efi / loader / arch / amd64 / framebuffer.c
1 /*-
2  * Copyright (c) 2013 The FreeBSD Foundation
3  * All rights reserved.
4  *
5  * This software was developed by Benno Rice under sponsorship from
6  * the FreeBSD Foundation.
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
17  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19  * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
20  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26  * SUCH DAMAGE.
27  */
28
29 #include <sys/cdefs.h>
30 __FBSDID("$FreeBSD$");
31
32 #include <bootstrap.h>
33 #include <sys/endian.h>
34 #include <stand.h>
35
36 #include <efi.h>
37 #include <efilib.h>
38 #include <efiuga.h>
39 #include <efipciio.h>
40 #include <machine/metadata.h>
41
42 #include "framebuffer.h"
43
44 static EFI_GUID gop_guid = EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID;
45 static EFI_GUID pciio_guid = EFI_PCI_IO_PROTOCOL_GUID;
46 static EFI_GUID uga_guid = EFI_UGA_DRAW_PROTOCOL_GUID;
47
48 static u_int
49 efifb_color_depth(struct efi_fb *efifb)
50 {
51         uint32_t mask;
52         u_int depth;
53
54         mask = efifb->fb_mask_red | efifb->fb_mask_green |
55             efifb->fb_mask_blue | efifb->fb_mask_reserved;
56         if (mask == 0)
57                 return (0);
58         for (depth = 1; mask != 1; depth++)
59                 mask >>= 1;
60         return (depth);
61 }
62
63 static int
64 efifb_mask_from_pixfmt(struct efi_fb *efifb, EFI_GRAPHICS_PIXEL_FORMAT pixfmt,
65     EFI_PIXEL_BITMASK *pixinfo)
66 {
67         int result;
68
69         result = 0;
70         switch (pixfmt) {
71         case PixelRedGreenBlueReserved8BitPerColor:
72                 efifb->fb_mask_red = 0x000000ff;
73                 efifb->fb_mask_green = 0x0000ff00;
74                 efifb->fb_mask_blue = 0x00ff0000;
75                 efifb->fb_mask_reserved = 0xff000000;
76                 break;
77         case PixelBlueGreenRedReserved8BitPerColor:
78                 efifb->fb_mask_red = 0x00ff0000;
79                 efifb->fb_mask_green = 0x0000ff00;
80                 efifb->fb_mask_blue = 0x000000ff;
81                 efifb->fb_mask_reserved = 0xff000000;
82                 break;
83         case PixelBitMask:
84                 efifb->fb_mask_red = pixinfo->RedMask;
85                 efifb->fb_mask_green = pixinfo->GreenMask;
86                 efifb->fb_mask_blue = pixinfo->BlueMask;
87                 efifb->fb_mask_reserved = pixinfo->ReservedMask;
88                 break;
89         default:
90                 result = 1;
91                 break;
92         }
93         return (result);
94 }
95
96 static int
97 efifb_from_gop(struct efi_fb *efifb, EFI_GRAPHICS_OUTPUT_PROTOCOL_MODE *mode,
98     EFI_GRAPHICS_OUTPUT_MODE_INFORMATION *info)
99 {
100         int result;
101
102         efifb->fb_addr = mode->FrameBufferBase;
103         efifb->fb_size = mode->FrameBufferSize;
104         efifb->fb_height = info->VerticalResolution;
105         efifb->fb_width = info->HorizontalResolution;
106         efifb->fb_stride = info->PixelsPerScanLine;
107         result = efifb_mask_from_pixfmt(efifb, info->PixelFormat,
108             &info->PixelInformation);
109         return (result);
110 }
111
112 static ssize_t
113 efifb_uga_find_pixel(EFI_UGA_DRAW_PROTOCOL *uga, u_int line,
114     EFI_PCI_IO_PROTOCOL *pciio, uint64_t addr, uint64_t size)
115 {
116         EFI_UGA_PIXEL pix0, pix1;
117         uint8_t *data1, *data2;
118         size_t count, maxcount = 1024;
119         ssize_t ofs;
120         EFI_STATUS status;
121         u_int idx;
122
123         status = uga->Blt(uga, &pix0, EfiUgaVideoToBltBuffer,
124             0, line, 0, 0, 1, 1, 0);
125         if (EFI_ERROR(status)) {
126                 printf("UGA BLT operation failed (video->buffer)");
127                 return (-1);
128         }
129         pix1.Red = ~pix0.Red;
130         pix1.Green = ~pix0.Green;
131         pix1.Blue = ~pix0.Blue;
132         pix1.Reserved = 0;
133
134         data1 = calloc(maxcount, 2);
135         if (data1 == NULL) {
136                 printf("Unable to allocate memory");
137                 return (-1);
138         }
139         data2 = data1 + maxcount;
140
141         ofs = 0;
142         while (size > 0) {
143                 count = min(size, maxcount);
144
145                 status = pciio->Mem.Read(pciio, EfiPciIoWidthUint32,
146                     EFI_PCI_IO_PASS_THROUGH_BAR, addr + ofs, count >> 2,
147                     data1);
148                 if (EFI_ERROR(status)) {
149                         printf("Error reading frame buffer (before)");
150                         goto fail;
151                 }
152                 status = uga->Blt(uga, &pix1, EfiUgaBltBufferToVideo,
153                     0, 0, 0, line, 1, 1, 0);
154                 if (EFI_ERROR(status)) {
155                         printf("UGA BLT operation failed (modify)");
156                         goto fail;
157                 }
158                 status = pciio->Mem.Read(pciio, EfiPciIoWidthUint32,
159                     EFI_PCI_IO_PASS_THROUGH_BAR, addr + ofs, count >> 2,
160                     data2);
161                 if (EFI_ERROR(status)) {
162                         printf("Error reading frame buffer (after)");
163                         goto fail;
164                 }
165                 status = uga->Blt(uga, &pix0, EfiUgaBltBufferToVideo,
166                     0, 0, 0, line, 1, 1, 0);
167                 if (EFI_ERROR(status)) {
168                         printf("UGA BLT operation failed (restore)");
169                         goto fail;
170                 }
171                 for (idx = 0; idx < count; idx++) {
172                         if (data1[idx] != data2[idx]) {
173                                 free(data1);
174                                 return (ofs + (idx & ~3));
175                         }
176                 }
177                 ofs += count;
178                 size -= count;
179         }
180         printf("No change detected in frame buffer");
181
182  fail:
183         printf(" -- error %lu\n", status & ~EFI_ERROR_MASK);
184         free(data1);
185         return (-1);
186 }
187
188 static EFI_PCI_IO_PROTOCOL *
189 efifb_uga_get_pciio(void)
190 {
191         EFI_PCI_IO_PROTOCOL *pciio;
192         EFI_HANDLE *buf, *hp;
193         EFI_STATUS status;
194         UINTN bufsz;
195
196         /* Get all handles that support the UGA protocol. */
197         bufsz = 0;
198         status = BS->LocateHandle(ByProtocol, &uga_guid, NULL, &bufsz, NULL);
199         if (status != EFI_BUFFER_TOO_SMALL)
200                 return (NULL);
201         buf = malloc(bufsz);
202         status = BS->LocateHandle(ByProtocol, &uga_guid, NULL, &bufsz, buf);
203         if (status != EFI_SUCCESS) {
204                 free(buf);
205                 return (NULL);
206         }
207         bufsz /= sizeof(EFI_HANDLE);
208
209         /* Get the PCI I/O interface of the first handle that supports it. */
210         pciio = NULL;
211         for (hp = buf; hp < buf + bufsz; hp++) {
212                 status = BS->HandleProtocol(*hp, &pciio_guid, (void **)&pciio);
213                 if (status == EFI_SUCCESS) {
214                         free(buf);
215                         return (pciio);
216                 }
217         }
218         free(buf);
219         return (NULL);
220 }
221
222 static EFI_STATUS
223 efifb_uga_locate_framebuffer(EFI_PCI_IO_PROTOCOL *pciio, uint64_t *addrp,
224     uint64_t *sizep)
225 {
226         uint8_t *resattr;
227         uint64_t addr, size;
228         EFI_STATUS status;
229         u_int bar;
230
231         if (pciio == NULL)
232                 return (EFI_DEVICE_ERROR);
233
234         /* Attempt to get the frame buffer address (imprecise). */
235         *addrp = 0;
236         *sizep = 0;
237         for (bar = 0; bar < 6; bar++) {
238                 status = pciio->GetBarAttributes(pciio, bar, NULL,
239                     (void **)&resattr);
240                 if (status != EFI_SUCCESS)
241                         continue;
242                 /* XXX magic offsets and constants. */
243                 if (resattr[0] == 0x87 && resattr[3] == 0) {
244                         /* 32-bit address space descriptor (MEMIO) */
245                         addr = le32dec(resattr + 10);
246                         size = le32dec(resattr + 22);
247                 } else if (resattr[0] == 0x8a && resattr[3] == 0) {
248                         /* 64-bit address space descriptor (MEMIO) */
249                         addr = le64dec(resattr + 14);
250                         size = le64dec(resattr + 38);
251                 } else {
252                         addr = 0;
253                         size = 0;
254                 }
255                 BS->FreePool(resattr);
256                 if (addr == 0 || size == 0)
257                         continue;
258
259                 /* We assume the largest BAR is the frame buffer. */
260                 if (size > *sizep) {
261                         *addrp = addr;
262                         *sizep = size;
263                 }
264         }
265         return ((*addrp == 0 || *sizep == 0) ? EFI_DEVICE_ERROR : 0);
266 }
267
268 static int
269 efifb_from_uga(struct efi_fb *efifb, EFI_UGA_DRAW_PROTOCOL *uga)
270 {
271         EFI_PCI_IO_PROTOCOL *pciio;
272         char *ev, *p;
273         EFI_STATUS status;
274         ssize_t offset;
275         uint64_t fbaddr;
276         uint32_t horiz, vert, stride;
277         uint32_t np, depth, refresh;
278
279         status = uga->GetMode(uga, &horiz, &vert, &depth, &refresh);
280         if (EFI_ERROR(status))
281                 return (1);
282         efifb->fb_height = vert;
283         efifb->fb_width = horiz;
284         /* Paranoia... */
285         if (efifb->fb_height == 0 || efifb->fb_width == 0)
286                 return (1);
287
288         /* The color masks are fixed AFAICT. */
289         efifb_mask_from_pixfmt(efifb, PixelBlueGreenRedReserved8BitPerColor,
290             NULL);
291
292         /* pciio can be NULL on return! */
293         pciio = efifb_uga_get_pciio();
294
295         /* Try to find the frame buffer. */
296         status = efifb_uga_locate_framebuffer(pciio, &efifb->fb_addr,
297             &efifb->fb_size);
298         if (EFI_ERROR(status)) {
299                 efifb->fb_addr = 0;
300                 efifb->fb_size = 0;
301         }
302
303         /*
304          * There's no reliable way to detect the frame buffer or the
305          * offset within the frame buffer of the visible region, nor
306          * the stride. Our only option is to look at the system and
307          * fill in the blanks based on that. Luckily, UGA was mostly
308          * only used on Apple hardware. 
309          */
310         offset = -1;
311         ev = getenv("smbios.system.maker");
312         if (ev != NULL && !strcmp(ev, "Apple Inc.")) {
313                 ev = getenv("smbios.system.product");
314                 if (ev != NULL && !strcmp(ev, "iMac7,1")) {
315                         /* These are the expected values we should have. */
316                         horiz = 1680;
317                         vert = 1050;
318                         fbaddr = 0xc0000000;
319                         /* These are the missing bits. */
320                         offset = 0x10000;
321                         stride = 1728;
322                 } else if (ev != NULL && !strcmp(ev, "MacBook3,1")) {
323                         /* These are the expected values we should have. */
324                         horiz = 1280;
325                         vert = 800;
326                         fbaddr = 0xc0000000;
327                         /* These are the missing bits. */
328                         offset = 0x0;
329                         stride = 2048;
330                 }
331         }
332
333         /*
334          * If this is hardware we know, make sure that it looks familiar
335          * before we accept our hardcoded values.
336          */
337         if (offset >= 0 && efifb->fb_width == horiz &&
338             efifb->fb_height == vert && efifb->fb_addr == fbaddr) {
339                 efifb->fb_addr += offset;
340                 efifb->fb_size -= offset;
341                 efifb->fb_stride = stride;
342                 return (0);
343         } else if (offset >= 0) {
344                 printf("Hardware make/model known, but graphics not "
345                     "as expected.\n");
346                 printf("Console may not work!\n");
347         }
348
349         /*
350          * The stride is equal or larger to the width. Often it's the
351          * next larger power of two. We'll start with that...
352          */
353         efifb->fb_stride = efifb->fb_width;
354         do {
355                 np = efifb->fb_stride & (efifb->fb_stride - 1);
356                 if (np) {
357                         efifb->fb_stride |= (np - 1);
358                         efifb->fb_stride++;
359                 }
360         } while (np);
361
362         ev = getenv("hw.efifb.address");
363         if (ev == NULL) {
364                 if (efifb->fb_addr == 0) {
365                         printf("Please set hw.efifb.address and "
366                             "hw.efifb.stride.\n");
367                         return (1);
368                 }
369
370                 /*
371                  * The visible part of the frame buffer may not start at
372                  * offset 0, so try to detect it. Note that we may not
373                  * always be able to read from the frame buffer, which
374                  * means that we may not be able to detect anything. In
375                  * that case, we would take a long time scanning for a
376                  * pixel change in the frame buffer, which would have it
377                  * appear that we're hanging, so we limit the scan to
378                  * 1/256th of the frame buffer. This number is mostly
379                  * based on PR 202730 and the fact that on a MacBoook,
380                  * where we can't read from the frame buffer the offset
381                  * of the visible region is 0. In short: we want to scan
382                  * enough to handle all adapters that have an offset
383                  * larger than 0 and we want to scan as little as we can
384                  * to not appear to hang when we can't read from the
385                  * frame buffer.
386                  */
387                 offset = efifb_uga_find_pixel(uga, 0, pciio, efifb->fb_addr,
388                     efifb->fb_size >> 8);
389                 if (offset == -1) {
390                         printf("Unable to reliably detect frame buffer.\n");
391                 } else if (offset > 0) {
392                         efifb->fb_addr += offset;
393                         efifb->fb_size -= offset;
394                 }
395         } else {
396                 offset = 0;
397                 efifb->fb_size = efifb->fb_height * efifb->fb_stride * 4;
398                 efifb->fb_addr = strtoul(ev, &p, 0);
399                 if (*p != '\0')
400                         return (1);
401         }
402
403         ev = getenv("hw.efifb.stride");
404         if (ev == NULL) {
405                 if (pciio != NULL && offset != -1) {
406                         /* Determine the stride. */
407                         offset = efifb_uga_find_pixel(uga, 1, pciio,
408                             efifb->fb_addr, horiz * 8);
409                         if (offset != -1)
410                                 efifb->fb_stride = offset >> 2;
411                 } else {
412                         printf("Unable to reliably detect the stride.\n");
413                 }
414         } else {
415                 efifb->fb_stride = strtoul(ev, &p, 0);
416                 if (*p != '\0')
417                         return (1);
418         }
419
420         /*
421          * We finalized on the stride, so recalculate the size of the
422          * frame buffer.
423          */
424         efifb->fb_size = efifb->fb_height * efifb->fb_stride * 4;
425         return (0);
426 }
427
428 int
429 efi_find_framebuffer(struct efi_fb *efifb)
430 {
431         EFI_GRAPHICS_OUTPUT *gop;
432         EFI_UGA_DRAW_PROTOCOL *uga;
433         EFI_STATUS status;
434
435         status = BS->LocateProtocol(&gop_guid, NULL, (VOID **)&gop);
436         if (status == EFI_SUCCESS)
437                 return (efifb_from_gop(efifb, gop->Mode, gop->Mode->Info));
438
439         status = BS->LocateProtocol(&uga_guid, NULL, (VOID **)&uga);
440         if (status == EFI_SUCCESS)
441                 return (efifb_from_uga(efifb, uga));
442
443         return (1);
444 }
445
446 static void
447 print_efifb(int mode, struct efi_fb *efifb, int verbose)
448 {
449         u_int depth;
450
451         if (mode >= 0)
452                 printf("mode %d: ", mode);
453         depth = efifb_color_depth(efifb);
454         printf("%ux%ux%u, stride=%u", efifb->fb_width, efifb->fb_height,
455             depth, efifb->fb_stride);
456         if (verbose) {
457                 printf("\n    frame buffer: address=%jx, size=%jx",
458                     (uintmax_t)efifb->fb_addr, (uintmax_t)efifb->fb_size);
459                 printf("\n    color mask: R=%08x, G=%08x, B=%08x\n",
460                     efifb->fb_mask_red, efifb->fb_mask_green,
461                     efifb->fb_mask_blue);
462         }
463 }
464
465 COMMAND_SET(gop, "gop", "graphics output protocol", command_gop);
466
467 static int
468 command_gop(int argc, char *argv[])
469 {
470         struct efi_fb efifb;
471         EFI_GRAPHICS_OUTPUT *gop;
472         EFI_STATUS status;
473         u_int mode;
474
475         status = BS->LocateProtocol(&gop_guid, NULL, (VOID **)&gop);
476         if (EFI_ERROR(status)) {
477                 sprintf(command_errbuf, "%s: Graphics Output Protocol not "
478                     "present (error=%lu)", argv[0], status & ~EFI_ERROR_MASK);
479                 return (CMD_ERROR);
480         }
481
482         if (argc < 2)
483                 goto usage;
484
485         if (!strcmp(argv[1], "set")) {
486                 char *cp;
487
488                 if (argc != 3)
489                         goto usage;
490                 mode = strtol(argv[2], &cp, 0);
491                 if (cp[0] != '\0') {
492                         sprintf(command_errbuf, "mode is an integer");
493                         return (CMD_ERROR);
494                 }
495                 status = gop->SetMode(gop, mode);
496                 if (EFI_ERROR(status)) {
497                         sprintf(command_errbuf, "%s: Unable to set mode to "
498                             "%u (error=%lu)", argv[0], mode,
499                             status & ~EFI_ERROR_MASK);
500                         return (CMD_ERROR);
501                 }
502         } else if (!strcmp(argv[1], "get")) {
503                 if (argc != 2)
504                         goto usage;
505                 efifb_from_gop(&efifb, gop->Mode, gop->Mode->Info);
506                 print_efifb(gop->Mode->Mode, &efifb, 1);
507                 printf("\n");
508         } else if (!strcmp(argv[1], "list")) {
509                 EFI_GRAPHICS_OUTPUT_MODE_INFORMATION *info;
510                 UINTN infosz;
511
512                 if (argc != 2)
513                         goto usage;
514                 pager_open();
515                 for (mode = 0; mode < gop->Mode->MaxMode; mode++) {
516                         status = gop->QueryMode(gop, mode, &infosz, &info);
517                         if (EFI_ERROR(status))
518                                 continue;
519                         efifb_from_gop(&efifb, gop->Mode, info);
520                         print_efifb(mode, &efifb, 0);
521                         if (pager_output("\n"))
522                                 break;
523                 }
524                 pager_close();
525         }
526         return (CMD_OK);
527
528  usage:
529         sprintf(command_errbuf, "usage: %s [list | get | set <mode>]",
530             argv[0]);
531         return (CMD_ERROR);
532 }
533
534 COMMAND_SET(uga, "uga", "universal graphics adapter", command_uga);
535
536 static int
537 command_uga(int argc, char *argv[])
538 {
539         struct efi_fb efifb;
540         EFI_UGA_DRAW_PROTOCOL *uga;
541         EFI_STATUS status;
542
543         status = BS->LocateProtocol(&uga_guid, NULL, (VOID **)&uga);
544         if (EFI_ERROR(status)) {
545                 sprintf(command_errbuf, "%s: UGA Protocol not present "
546                     "(error=%lu)", argv[0], status & ~EFI_ERROR_MASK);
547                 return (CMD_ERROR);
548         }
549
550         if (argc != 1)
551                 goto usage;
552
553         if (efifb_from_uga(&efifb, uga) != CMD_OK) {
554                 sprintf(command_errbuf, "%s: Unable to get UGA information",
555                     argv[0]);
556                 return (CMD_ERROR);
557         }
558
559         print_efifb(-1, &efifb, 1);
560         printf("\n");
561         return (CMD_OK);
562
563  usage:
564         sprintf(command_errbuf, "usage: %s", argv[0]);
565         return (CMD_ERROR);
566 }