#include <sys/cdefs.h>
#include <stand.h>
#include <bootstrap.h>
#include <sys/endian.h>
#include <sys/param.h>
#include <sys/font.h>
#include <sys/consplat.h>
#include <sys/limits.h>
#include <efi.h>
#include <efilib.h>
#include <eficonsctl.h>
#include <Guid/ConsoleOutDevice.h>
#include <Protocol/GraphicsOutput.h>
#include <Protocol/UgaDraw.h>
#include <Protocol/UgaIo.h>
#include <Protocol/EdidActive.h>
#include <Protocol/EdidDiscovered.h>
#include <Protocol/PciIo.h>
#include <machine/metadata.h>
#include "gfx_fb.h"
#include "framebuffer.h"
EFI_GUID gEfiGraphicsOutputProtocolGuid = EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID;
EFI_GUID gEfiPciIoProtocolGuid = EFI_PCI_IO_PROTOCOL_GUID;
EFI_GUID gEfiUgaDrawProtocolGuid = EFI_UGA_DRAW_PROTOCOL_GUID;
EFI_GUID gEfiUgaIoProtocolGuid = EFI_UGA_IO_PROTOCOL_GUID;
EFI_GUID gEfiEdidActiveProtocolGuid = EFI_EDID_ACTIVE_PROTOCOL_GUID;
EFI_GUID gEfiEdidDiscoveredProtocolGuid = EFI_EDID_DISCOVERED_PROTOCOL_GUID;
static EFI_HANDLE gop_handle;
static uint32_t default_mode = UINT32_MAX;
struct vesa_edid_info *edid_info = NULL;
static uint32_t gop_default_mode(void);
static int efifb_set_mode(EFI_GRAPHICS_OUTPUT_PROTOCOL *, uint_t);
static uint_t
efifb_color_depth(struct efi_fb *efifb)
{
uint32_t mask;
uint_t depth;
mask = efifb->fb_mask_red | efifb->fb_mask_green |
efifb->fb_mask_blue | efifb->fb_mask_reserved;
if (mask == 0)
return (0);
for (depth = 1; mask != 1; depth++)
mask >>= 1;
return (depth);
}
static int
efifb_mask_from_pixfmt(struct efi_fb *efifb, EFI_GRAPHICS_PIXEL_FORMAT pixfmt,
EFI_PIXEL_BITMASK *pixinfo)
{
int result;
result = 0;
switch (pixfmt) {
case PixelRedGreenBlueReserved8BitPerColor:
case PixelBltOnly:
efifb->fb_mask_red = 0x000000ff;
efifb->fb_mask_green = 0x0000ff00;
efifb->fb_mask_blue = 0x00ff0000;
efifb->fb_mask_reserved = 0xff000000;
break;
case PixelBlueGreenRedReserved8BitPerColor:
efifb->fb_mask_red = 0x00ff0000;
efifb->fb_mask_green = 0x0000ff00;
efifb->fb_mask_blue = 0x000000ff;
efifb->fb_mask_reserved = 0xff000000;
break;
case PixelBitMask:
efifb->fb_mask_red = pixinfo->RedMask;
efifb->fb_mask_green = pixinfo->GreenMask;
efifb->fb_mask_blue = pixinfo->BlueMask;
efifb->fb_mask_reserved = pixinfo->ReservedMask;
break;
default:
result = 1;
break;
}
return (result);
}
static int
efifb_from_gop(struct efi_fb *efifb, EFI_GRAPHICS_OUTPUT_PROTOCOL_MODE *mode,
EFI_GRAPHICS_OUTPUT_MODE_INFORMATION *info)
{
int result;
efifb->fb_addr = mode->FrameBufferBase;
efifb->fb_size = mode->FrameBufferSize;
efifb->fb_height = info->VerticalResolution;
efifb->fb_width = info->HorizontalResolution;
efifb->fb_stride = info->PixelsPerScanLine;
result = efifb_mask_from_pixfmt(efifb, info->PixelFormat,
&info->PixelInformation);
if (efifb->fb_addr == 0)
result = 1;
return (result);
}
static ssize_t
efifb_uga_find_pixel(EFI_UGA_DRAW_PROTOCOL *uga, uint_t line,
EFI_PCI_IO_PROTOCOL *pciio, uint64_t addr, uint64_t size)
{
EFI_UGA_PIXEL pix0, pix1;
uint8_t *data1, *data2;
size_t count, maxcount = 1024;
ssize_t ofs;
EFI_STATUS status;
uint_t idx;
status = uga->Blt(uga, &pix0, EfiUgaVideoToBltBuffer,
0, line, 0, 0, 1, 1, 0);
if (EFI_ERROR(status)) {
printf("UGA BLT operation failed (video->buffer)");
return (-1);
}
pix1.Red = ~pix0.Red;
pix1.Green = ~pix0.Green;
pix1.Blue = ~pix0.Blue;
pix1.Reserved = 0;
data1 = calloc(maxcount, 2);
if (data1 == NULL) {
printf("Unable to allocate memory");
return (-1);
}
data2 = data1 + maxcount;
ofs = 0;
while (size > 0) {
count = min(size, maxcount);
status = pciio->Mem.Read(pciio, EfiPciIoWidthUint32,
EFI_PCI_IO_PASS_THROUGH_BAR, addr + ofs, count >> 2,
data1);
if (EFI_ERROR(status)) {
printf("Error reading frame buffer (before)");
goto fail;
}
status = uga->Blt(uga, &pix1, EfiUgaBltBufferToVideo,
0, 0, 0, line, 1, 1, 0);
if (EFI_ERROR(status)) {
printf("UGA BLT operation failed (modify)");
goto fail;
}
status = pciio->Mem.Read(pciio, EfiPciIoWidthUint32,
EFI_PCI_IO_PASS_THROUGH_BAR, addr + ofs, count >> 2,
data2);
if (EFI_ERROR(status)) {
printf("Error reading frame buffer (after)");
goto fail;
}
status = uga->Blt(uga, &pix0, EfiUgaBltBufferToVideo,
0, 0, 0, line, 1, 1, 0);
if (EFI_ERROR(status)) {
printf("UGA BLT operation failed (restore)");
goto fail;
}
for (idx = 0; idx < count; idx++) {
if (data1[idx] != data2[idx]) {
free(data1);
return (ofs + (idx & ~3));
}
}
ofs += count;
size -= count;
}
printf("No change detected in frame buffer");
fail:
printf(" -- error %lu\n", DECODE_ERROR(status));
free(data1);
return (-1);
}
static EFI_PCI_IO_PROTOCOL *
efifb_uga_get_pciio(void)
{
EFI_PCI_IO_PROTOCOL *pciio;
EFI_HANDLE *handles;
EFI_STATUS status;
uint_t i, nhandles;
status = efi_get_protocol_handles(&gEfiUgaDrawProtocolGuid,
&nhandles, &handles);
if (status != EFI_SUCCESS) {
return (NULL);
}
pciio = NULL;
for (i = 0; i < nhandles; i++) {
status = OpenProtocolByHandle(handles[i],
&gEfiPciIoProtocolGuid, (void **)&pciio);
if (status == EFI_SUCCESS) {
free(handles);
return (pciio);
}
}
free(handles);
return (NULL);
}
static EFI_STATUS
efifb_uga_locate_framebuffer(EFI_PCI_IO_PROTOCOL *pciio, uint64_t *addrp,
uint64_t *sizep)
{
uint8_t *resattr;
uint64_t addr, size;
EFI_STATUS status;
uint_t bar;
if (pciio == NULL)
return (EFI_DEVICE_ERROR);
*addrp = 0;
*sizep = 0;
for (bar = 0; bar < 6; bar++) {
status = pciio->GetBarAttributes(pciio, bar, NULL,
(void **)&resattr);
if (status != EFI_SUCCESS)
continue;
if (resattr[0] == 0x87 && resattr[3] == 0) {
addr = le32dec(resattr + 10);
size = le32dec(resattr + 22);
} else if (resattr[0] == 0x8a && resattr[3] == 0) {
addr = le64dec(resattr + 14);
size = le64dec(resattr + 38);
} else {
addr = 0;
size = 0;
}
BS->FreePool(resattr);
if (addr == 0 || size == 0)
continue;
if (size > *sizep) {
*addrp = addr;
*sizep = size;
}
}
return ((*addrp == 0 || *sizep == 0) ? EFI_DEVICE_ERROR : 0);
}
static int
efifb_from_uga(struct efi_fb *efifb, EFI_UGA_DRAW_PROTOCOL *uga)
{
EFI_PCI_IO_PROTOCOL *pciio;
char *ev, *p;
EFI_STATUS status;
ssize_t offset;
uint64_t fbaddr;
uint32_t horiz, vert, stride;
uint32_t np, depth, refresh;
status = uga->GetMode(uga, &horiz, &vert, &depth, &refresh);
if (EFI_ERROR(status))
return (1);
efifb->fb_height = vert;
efifb->fb_width = horiz;
if (efifb->fb_height == 0 || efifb->fb_width == 0)
return (1);
efifb_mask_from_pixfmt(efifb, PixelBlueGreenRedReserved8BitPerColor,
NULL);
pciio = efifb_uga_get_pciio();
status = efifb_uga_locate_framebuffer(pciio, &efifb->fb_addr,
&efifb->fb_size);
if (EFI_ERROR(status)) {
efifb->fb_addr = 0;
efifb->fb_size = 0;
}
offset = -1;
ev = getenv("smbios.system.maker");
if (ev != NULL && strcmp(ev, "Apple Inc.") == 0) {
ev = getenv("smbios.system.product");
if (ev != NULL && strcmp(ev, "iMac7,1") == 0) {
horiz = 1680;
vert = 1050;
fbaddr = 0xc0000000;
offset = 0x10000;
stride = 1728;
} else if (ev != NULL && strcmp(ev, "MacBook3,1") == 0) {
horiz = 1280;
vert = 800;
fbaddr = 0xc0000000;
offset = 0x0;
stride = 2048;
}
}
if (offset >= 0 && efifb->fb_width == horiz &&
efifb->fb_height == vert && efifb->fb_addr == fbaddr) {
efifb->fb_addr += offset;
efifb->fb_size -= offset;
efifb->fb_stride = stride;
return (0);
} else if (offset >= 0) {
printf("Hardware make/model known, but graphics not "
"as expected.\n");
printf("Console may not work!\n");
}
efifb->fb_stride = efifb->fb_width;
do {
np = efifb->fb_stride & (efifb->fb_stride - 1);
if (np) {
efifb->fb_stride |= (np - 1);
efifb->fb_stride++;
}
} while (np);
ev = getenv("hw.efifb.address");
if (ev == NULL) {
if (efifb->fb_addr == 0) {
printf("Please set hw.efifb.address and "
"hw.efifb.stride.\n");
return (1);
}
offset = efifb_uga_find_pixel(uga, 0, pciio, efifb->fb_addr,
efifb->fb_size >> 8);
if (offset == -1) {
printf("Unable to reliably detect frame buffer.\n");
} else if (offset > 0) {
efifb->fb_addr += offset;
efifb->fb_size -= offset;
}
} else {
offset = 0;
efifb->fb_size = efifb->fb_height * efifb->fb_stride * 4;
efifb->fb_addr = strtoul(ev, &p, 0);
if (*p != '\0')
return (1);
}
ev = getenv("hw.efifb.stride");
if (ev == NULL) {
if (pciio != NULL && offset != -1) {
offset = efifb_uga_find_pixel(uga, 1, pciio,
efifb->fb_addr, horiz * 8);
if (offset != -1)
efifb->fb_stride = offset >> 2;
} else {
printf("Unable to reliably detect the stride.\n");
}
} else {
efifb->fb_stride = strtoul(ev, &p, 0);
if (*p != '\0')
return (1);
}
efifb->fb_size = efifb->fb_height * efifb->fb_stride * 4;
if (efifb->fb_addr == 0)
return (1);
return (0);
}
static struct vesa_edid_info *
efifb_gop_get_edid(EFI_HANDLE h)
{
const uint8_t magic[] = EDID_MAGIC;
EFI_EDID_ACTIVE_PROTOCOL *edid;
struct vesa_edid_info *edid_infop;
EFI_GUID *guid;
EFI_STATUS status;
size_t size;
guid = &gEfiEdidActiveProtocolGuid;
status = BS->OpenProtocol(h, guid, (void **)&edid, IH, NULL,
EFI_OPEN_PROTOCOL_GET_PROTOCOL);
if (status != EFI_SUCCESS ||
edid->SizeOfEdid == 0) {
guid = &gEfiEdidDiscoveredProtocolGuid;
status = BS->OpenProtocol(h, guid, (void **)&edid, IH, NULL,
EFI_OPEN_PROTOCOL_GET_PROTOCOL);
if (status != EFI_SUCCESS ||
edid->SizeOfEdid == 0)
return (NULL);
}
size = MAX(sizeof (*edid_infop), edid->SizeOfEdid);
edid_infop = calloc(1, size);
if (edid_infop == NULL)
return (NULL);
memcpy(edid_infop, edid->Edid, edid->SizeOfEdid);
if (memcmp(edid_infop, magic, sizeof (magic)) != 0)
goto error;
if (edid_infop->header.version != 1)
goto error;
return (edid_infop);
error:
free(edid_infop);
return (NULL);
}
static bool
efifb_get_edid(edid_res_list_t *res)
{
bool rv = false;
if (edid_info == NULL)
edid_info = efifb_gop_get_edid(gop_handle);
if (edid_info != NULL)
rv = gfx_get_edid_resolution(edid_info, res);
return (rv);
}
int
efi_find_framebuffer(struct efi_fb *efifb)
{
EFI_HANDLE *hlist;
uint_t nhandles, i;
extern EFI_GRAPHICS_OUTPUT_PROTOCOL *gop;
extern EFI_UGA_DRAW_PROTOCOL *uga;
EFI_STATUS status;
uint32_t mode;
if (gop != NULL)
return (efifb_from_gop(efifb, gop->Mode, gop->Mode->Info));
status = efi_get_protocol_handles(&gEfiGraphicsOutputProtocolGuid,
&nhandles, &hlist);
if (EFI_ERROR(status))
return (efi_status_to_errno(status));
gop_handle = NULL;
for (i = 0; i < nhandles; i++) {
EFI_GRAPHICS_OUTPUT_PROTOCOL *tgop;
void *dummy;
status = OpenProtocolByHandle(hlist[i],
&gEfiGraphicsOutputProtocolGuid,
(void **)&tgop);
if (status != EFI_SUCCESS)
continue;
if (tgop->Mode->Info->PixelFormat == PixelBltOnly ||
tgop->Mode->Info->PixelFormat >= PixelFormatMax)
continue;
status = OpenProtocolByHandle(hlist[i],
&gEfiConsoleOutDeviceGuid, &dummy);
if (status == EFI_SUCCESS) {
gop_handle = hlist[i];
gop = tgop;
break;
} else if (gop_handle == NULL) {
gop_handle = hlist[i];
gop = tgop;
}
}
free(hlist);
if (gop != NULL) {
if (default_mode == UINT32_MAX) {
default_mode = gop->Mode->Mode;
}
mode = gop_default_mode();
if (mode != gop->Mode->Mode)
efifb_set_mode(gop, mode);
return (efifb_from_gop(efifb, gop->Mode, gop->Mode->Info));
}
if (uga != NULL)
return (efifb_from_uga(efifb, uga));
status = BS->LocateProtocol(&gEfiUgaDrawProtocolGuid, NULL,
(void **)&uga);
if (status == EFI_SUCCESS)
return (efifb_from_uga(efifb, uga));
return (1);
}
static void
print_efifb(int mode, struct efi_fb *efifb, int verbose)
{
uint_t depth;
edid_res_list_t res;
struct resolution *rp;
TAILQ_INIT(&res);
if (verbose == 1) {
printf("Framebuffer mode: %s\n",
plat_stdout_is_framebuffer() ? "on" : "off");
if (efifb_get_edid(&res)) {
printf("EDID");
while ((rp = TAILQ_FIRST(&res)) != NULL) {
printf(" %dx%d", rp->width, rp->height);
TAILQ_REMOVE(&res, rp, next);
free(rp);
}
printf("\n");
}
}
if (mode >= 0) {
if (verbose == 1)
printf("GOP ");
printf("mode %d: ", mode);
}
depth = efifb_color_depth(efifb);
printf("%ux%ux%u", efifb->fb_width, efifb->fb_height, depth);
if (verbose)
printf(", stride=%u", efifb->fb_stride);
if (verbose) {
printf("\n frame buffer: address=%jx, size=%jx",
(uintmax_t)efifb->fb_addr, (uintmax_t)efifb->fb_size);
printf("\n color mask: R=%08x, G=%08x, B=%08x\n",
efifb->fb_mask_red, efifb->fb_mask_green,
efifb->fb_mask_blue);
if (efifb->fb_addr == 0) {
printf("Warning: this mode is not implementing the "
"linear framebuffer. The illumos\n\tconsole is "
"not available with this mode and will default to "
"ttya\n");
}
}
}
static int
efifb_set_mode(EFI_GRAPHICS_OUTPUT_PROTOCOL *gop, uint_t mode)
{
EFI_STATUS status;
status = gop->SetMode(gop, mode);
if (EFI_ERROR(status)) {
snprintf(command_errbuf, sizeof (command_errbuf),
"Unable to set mode to %u (error=%lu)",
mode, DECODE_ERROR(status));
return (CMD_ERROR);
}
return (CMD_OK);
}
static int
efifb_find_mode_xydm(UINT32 x, UINT32 y, int depth, int m)
{
extern EFI_GRAPHICS_OUTPUT_PROTOCOL *gop;
EFI_GRAPHICS_OUTPUT_MODE_INFORMATION *info;
EFI_STATUS status;
UINTN infosz;
struct efi_fb fb;
UINT32 mode;
uint_t d, i;
if (m != -1)
i = 8;
else if (depth == -1)
i = 32;
else
i = depth;
while (i > 0) {
for (mode = 0; mode < gop->Mode->MaxMode; mode++) {
status = gop->QueryMode(gop, mode, &infosz, &info);
if (EFI_ERROR(status))
continue;
if (m != -1) {
if ((UINT32)m == mode)
return (mode);
else
continue;
}
efifb_from_gop(&fb, gop->Mode, info);
d = efifb_color_depth(&fb);
if (x == fb.fb_width && y == fb.fb_height && d == i)
return (mode);
}
if (depth != -1)
break;
i -= 8;
}
return (gop->Mode->MaxMode);
}
static int
efifb_find_mode(char *str)
{
extern EFI_GRAPHICS_OUTPUT_PROTOCOL *gop;
int x, y, depth;
if (!gfx_parse_mode_str(str, &x, &y, &depth))
return (gop->Mode->MaxMode);
return (efifb_find_mode_xydm(x, y, depth, -1));
}
static uint32_t
gop_default_mode(void)
{
edid_res_list_t res;
struct resolution *rp;
extern EFI_GRAPHICS_OUTPUT_PROTOCOL *gop;
UINT32 mode;
mode = gop->Mode->MaxMode;
TAILQ_INIT(&res);
if (efifb_get_edid(&res)) {
while ((rp = TAILQ_FIRST(&res)) != NULL) {
if (mode == gop->Mode->MaxMode) {
mode = efifb_find_mode_xydm(
rp->width, rp->height, -1, -1);
}
TAILQ_REMOVE(&res, rp, next);
free(rp);
}
}
if (mode == gop->Mode->MaxMode)
mode = default_mode;
return (mode);
}
COMMAND_SET(framebuffer, "framebuffer", "framebuffer mode management",
command_gop);
static int
command_gop(int argc, char *argv[])
{
extern struct efi_fb efifb;
extern EFI_GRAPHICS_OUTPUT_PROTOCOL *gop;
struct efi_fb fb;
EFI_STATUS status;
char *arg, *cp;
uint_t mode;
if (gop == NULL) {
snprintf(command_errbuf, sizeof (command_errbuf),
"%s: Graphics Output Protocol not present", argv[0]);
return (CMD_ERROR);
}
if (argc < 2)
goto usage;
if (strcmp(argv[1], "off") == 0) {
if (argc != 2)
goto usage;
reset_font_flags();
plat_cons_update_mode(EfiConsoleControlScreenText);
return (CMD_OK);
}
if (strcmp(argv[1], "on") == 0) {
if (argc != 2)
goto usage;
reset_font_flags();
mode = gop_default_mode();
if (mode != gop->Mode->Mode)
efifb_set_mode(gop, mode);
plat_cons_update_mode(EfiConsoleControlScreenGraphics);
return (CMD_OK);
}
if (strcmp(argv[1], "set") == 0) {
int rv;
if (argc != 3)
goto usage;
arg = argv[2];
if (strchr(arg, 'x') == NULL) {
errno = 0;
mode = strtoul(arg, &cp, 0);
if (errno != 0 || *arg == '\0' || cp[0] != '\0') {
snprintf(command_errbuf,
sizeof (command_errbuf),
"mode should be an integer");
return (CMD_ERROR);
}
mode = efifb_find_mode_xydm(0, 0, 0, mode);
} else {
mode = efifb_find_mode(arg);
}
if (mode == gop->Mode->MaxMode)
mode = gop->Mode->Mode;
reset_font_flags();
rv = efifb_set_mode(gop, mode);
plat_cons_update_mode(EfiConsoleControlScreenGraphics);
return (rv);
}
if (strcmp(argv[1], "get") == 0) {
if (argc != 2)
goto usage;
print_efifb(gop->Mode->Mode, &efifb, 1);
printf("\n");
return (CMD_OK);
}
if (strcmp(argv[1], "list") == 0) {
EFI_GRAPHICS_OUTPUT_MODE_INFORMATION *info;
UINTN infosz;
int depth, d = -1;
if (argc != 2 && argc != 3)
goto usage;
if (argc == 3) {
arg = argv[2];
errno = 0;
d = strtoul(arg, &cp, 0);
if (errno != 0 || *arg == '\0' || cp[0] != '\0') {
snprintf(command_errbuf,
sizeof (command_errbuf),
"depth should be an integer");
return (CMD_ERROR);
}
}
pager_open();
for (mode = 0; mode < gop->Mode->MaxMode; mode++) {
status = gop->QueryMode(gop, mode, &infosz, &info);
if (EFI_ERROR(status))
continue;
efifb_from_gop(&fb, gop->Mode, info);
depth = efifb_color_depth(&fb);
if (d != -1 && d != depth)
continue;
print_efifb(mode, &fb, 0);
if (pager_output("\n"))
break;
}
pager_close();
return (CMD_OK);
}
usage:
snprintf(command_errbuf, sizeof (command_errbuf),
"usage: %s on | off | get | list [depth] | "
"set <display or GOP mode number>", argv[0]);
return (CMD_ERROR);
}
COMMAND_SET(uga, "uga", "universal graphics adapter", command_uga);
static int
command_uga(int argc, char *argv[])
{
extern struct efi_fb efifb;
extern EFI_UGA_DRAW_PROTOCOL *uga;
if (uga == NULL) {
snprintf(command_errbuf, sizeof (command_errbuf),
"%s: UGA Protocol not present", argv[0]);
return (CMD_ERROR);
}
if (argc != 1)
goto usage;
if (efifb.fb_addr == 0) {
snprintf(command_errbuf, sizeof (command_errbuf),
"%s: Unable to get UGA information", argv[0]);
return (CMD_ERROR);
}
print_efifb(-1, &efifb, 1);
printf("\n");
return (CMD_OK);
usage:
snprintf(command_errbuf, sizeof (command_errbuf), "usage: %s", argv[0]);
return (CMD_ERROR);
}