Download raw body.
efiboot: UGA fb fallback for EFI 1.1
Kind ping. Would like to see this one committed.
On Sat, 12 Sep 2026, at 13:59, Hugo Osvaldo Barrera wrote:
> Apple EFI 1.10 (Intel iMacs) provides no GOP, only UGA. There's no
> framebuffer, and no console. There's not output past the bootloader, and
> the device reboots 20 seconds after handoff.
>
> Implement UGA framebuffer. Kernel output shows up fine, host runs fine.
> Running for a couple of months already. efiuga.h comes from FreeBSD.
>
> Tested on an iMac5,1. The other offsets I found online from other folk's
> tests. We can drop them if it's preferable to have them fully tested
> on-device first.
> ---
> sys/arch/amd64/stand/efiboot/cmd_i386.c | 1 +
> sys/arch/amd64/stand/efiboot/efiboot.c | 148 +++++++++++++++++++++
> sys/arch/amd64/stand/efiboot/efiboot.h | 1 +
> sys/stand/efi/include/efiuga.h | 167 ++++++++++++++++++++++++
> 4 files changed, 317 insertions(+)
> create mode 100644 sys/stand/efi/include/efiuga.h
>
> diff --git a/sys/arch/amd64/stand/efiboot/cmd_i386.c
> b/sys/arch/amd64/stand/efiboot/cmd_i386.c
> index 5adacda3a42..7bb88c37d2b 100644
> --- a/sys/arch/amd64/stand/efiboot/cmd_i386.c
> +++ b/sys/arch/amd64/stand/efiboot/cmd_i386.c
> @@ -57,6 +57,7 @@ const struct cmd_table cmd_machine[] = {
> { "memory", CMDT_CMD, Xmemory },
> { "video", CMDT_CMD, Xvideo_efi },
> { "gop", CMDT_CMD, Xgop_efi },
> + { "fb", CMDT_CMD, Xfb_efi },
> { "exit", CMDT_CMD, Xexit_efi },
> { "poweroff", CMDT_CMD, Xpoweroff_efi },
> #ifdef DEBUG
> diff --git a/sys/arch/amd64/stand/efiboot/efiboot.c
> b/sys/arch/amd64/stand/efiboot/efiboot.c
> index 0401522e3a8..14aaf0023c2 100644
> --- a/sys/arch/amd64/stand/efiboot/efiboot.c
> +++ b/sys/arch/amd64/stand/efiboot/efiboot.c
> @@ -33,6 +33,7 @@
> #include <efiapi.h>
> #include <efiprot.h>
> #include <eficonsctl.h>
> +#include <efiuga.h>
>
> #include "efidev.h"
> #include "efiboot.h"
> @@ -443,10 +444,13 @@ efi_update_bios_memmap(void)
> static SIMPLE_TEXT_OUTPUT_INTERFACE *conout = NULL;
> static SIMPLE_INPUT_INTERFACE *conin;
> static EFI_GRAPHICS_OUTPUT *gop = NULL;
> +static EFI_UGA_DRAW_PROTOCOL *uga = NULL;
> static EFI_GUID con_guid
> = EFI_CONSOLE_CONTROL_PROTOCOL_GUID;
> static EFI_GUID gop_guid
> = EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID;
> +static EFI_GUID uga_guid
> + = EFI_UGA_DRAW_PROTOCOL_GUID;
> static EFI_GUID serio_guid
> = SERIAL_IO_PROTOCOL;
> struct efi_video {
> @@ -484,6 +488,9 @@ efi_video_init(void)
> BS->FreePool(handles);
> }
>
> + if (gop == NULL)
> + BS->LocateProtocol(&uga_guid, NULL, (void **)&uga);
> +
> conout = ST->ConOut;
> status = BS->LocateProtocol(&con_guid, NULL, (void **)&conctrl);
> if (status == EFI_SUCCESS)
> @@ -864,6 +871,97 @@ efi_gop_setmode(int mode)
> return (status);
> }
>
> +/*
> + * UGA provides no way to discover the fb address.
> + */
> +struct uga_fb_quirk {
> + const char *product; /* SMBIOS product name */
> + u_long addr;
> + int width, height, stride;
> +};
> +
> +static const struct uga_fb_quirk uga_fb_quirks[] = {
> + { "MacBook1,1", 0x80000000, 1280, 800, 2048 },
> + { "MacBook2,1", 0x80000000, 1280, 800, 2048 },
> + { "MacBook3,1", 0x80000000, 1280, 800, 2048 },
> + { "MacBook4,1", 0x80000000, 1280, 800, 2048 },
> + { "MacBookPro1,1", 0x80010000, 1440, 900, 1472 },
> + { "MacBookPro2,2", 0x80010000, 1440, 900, 1472 },
> + { "MacBookPro3,1", 0x80030000, 1440, 900, 2048 },
> + { "Macmini1,1", 0x80000000, 1024, 768, 2048 },
> + { "iMac4,1", 0x80010000, 1440, 900, 1472 },
> + { "iMac5,1", 0xc0010000, 1680, 1050, 1728 },
> +};
> +
> +static u_long uga_fb_addr;
> +static int uga_fb_width, uga_fb_height, uga_fb_ppsl;
> +
> +static const char *
> +efi_smbios_product(void)
> +{
> + static char prod[48];
> + uint8_t *eps = NULL, *p, *q, *end;
> + uint32_t staddr;
> + uint16_t stlen;
> + int i, n;
> +
> + for (i = 0; i < ST->NumberOfTableEntries; i++) {
> + if (efi_guidcmp(&smbios_guid,
> + &ST->ConfigurationTable[i].VendorGuid) == 0)
> + eps = ST->ConfigurationTable[i].VendorTable;
> + }
> + if (eps == NULL || memcmp(eps, "_SM_", 4) != 0)
> + return (NULL);
> + memcpy(&stlen, eps + 0x16, sizeof(stlen));
> + memcpy(&staddr, eps + 0x18, sizeof(staddr));
> + p = (uint8_t *)(u_long)staddr;
> + end = p + stlen;
> +
> + while (p + 4 <= end && p[1] >= 4) {
> + q = p + p[1];
> + if (p[0] == 1 && p[1] > 5) { /* System Information */
> + n = p[5];
> + if (n == 0)
> + return (NULL);
> + for (i = 1; i < n && q < end; i++)
> + q += strlen((char *)q) + 1;
> + if (q >= end || *q == '\0')
> + return (NULL);
> + snprintf(prod, sizeof(prod), "%s", (char *)q);
> + return (prod);
> + }
> + while (q + 1 < end && (q[0] != 0 || q[1] != 0))
> + q++;
> + p = q + 2;
> + }
> + return (NULL);
> +}
> +
> +static void
> +uga_fb_resolve(void)
> +{
> + const struct uga_fb_quirk *fq;
> + const char *prod;
> + int i;
> +
> + if (uga_fb_addr != 0)
> + return;
> +
> + prod = efi_smbios_product();
> + if (prod == NULL)
> + return;
> + for (i = 0; i < nitems(uga_fb_quirks); i++) {
> + fq = &uga_fb_quirks[i];
> + if (strcmp(prod, fq->product) == 0) {
> + uga_fb_addr = fq->addr;
> + uga_fb_width = fq->width;
> + uga_fb_height = fq->height;
> + uga_fb_ppsl = fq->stride;
> + break;
> + }
> + }
> +}
> +
> void
> efi_makebootargs(void)
> {
> @@ -969,6 +1067,21 @@ efi_makebootargs(void)
> ei->fb_height = gopi->VerticalResolution;
> ei->fb_width = gopi->HorizontalResolution;
> ei->fb_pixpsl = gopi->PixelsPerScanLine;
> + } else if (uga != NULL) {
> + uga_fb_resolve();
> + if (uga_fb_addr != 0 && uga_fb_width > 0 &&
> + uga_fb_height > 0 && uga_fb_ppsl > 0) {
> + ei->fb_red_mask = 0x00ff0000;
> + ei->fb_green_mask = 0x0000ff00;
> + ei->fb_blue_mask = 0x000000ff;
> + ei->fb_reserved_mask = 0xff000000;
> + ei->fb_addr = uga_fb_addr;
> + ei->fb_width = uga_fb_width;
> + ei->fb_height = uga_fb_height;
> + ei->fb_pixpsl = uga_fb_ppsl;
> + ei->fb_size = (uint64_t)uga_fb_height *
> + uga_fb_ppsl * 4;
> + }
> }
>
> /*
> @@ -1242,6 +1355,41 @@ Xgop_efi(void)
> return (0);
> }
>
> +int
> +Xfb_efi(void)
> +{
> + UINT32 hres = 0, vres = 0, depth = 0, refresh = 0;
> + const char *prod;
> +
> + if (cmd.argc >= 5) {
> + uga_fb_addr = (u_long)strtoll(cmd.argv[1], NULL, 0);
> + uga_fb_width = strtol(cmd.argv[2], NULL, 0);
> + uga_fb_height = strtol(cmd.argv[3], NULL, 0);
> + uga_fb_ppsl = strtol(cmd.argv[4], NULL, 0);
> + } else if (cmd.argc != 1) {
> + printf("fb addr width height stride_pixels\n"
> + "e.g. fb 0xc0010000 1680 1050 1728\n");
> + return (0);
> + }
> +
> + uga_fb_resolve();
> +
> + prod = efi_smbios_product();
> + printf("smbios product: %s\n", prod == NULL ? "(unknown)" : prod);
> + if (uga != NULL &&
> + uga->GetMode(uga, &hres, &vres, &depth, &refresh) == EFI_SUCCESS)
> + printf("uga mode: %ux%u depth %u refresh %u\n", hres, vres,
> + depth, refresh);
> + if (uga_fb_addr != 0)
> + printf("kernel fb: addr 0x%lx %dx%d stride %d\n",
> + uga_fb_addr, uga_fb_width, uga_fb_height, uga_fb_ppsl);
> + else
> + printf("kernel fb: none (no quirk match; set with "
> + "'machine fb')\n");
> +
> + return (0);
> +}
> +
> #ifdef IDLE_POWEROFF
> int
> Xidle_efi(void)
> diff --git a/sys/arch/amd64/stand/efiboot/efiboot.h
> b/sys/arch/amd64/stand/efiboot/efiboot.h
> index 66515e50aa2..68862240919 100644
> --- a/sys/arch/amd64/stand/efiboot/efiboot.h
> +++ b/sys/arch/amd64/stand/efiboot/efiboot.h
> @@ -32,6 +32,7 @@ int efi_com_getc(dev_t);
> void efi_com_putc(dev_t, int);
> int Xvideo_efi(void);
> int Xgop_efi(void);
> +int Xfb_efi(void);
> int Xexit_efi(void);
> void efi_makebootargs(void);
> void efi_setconsdev(void);
> diff --git a/sys/stand/efi/include/efiuga.h
> b/sys/stand/efi/include/efiuga.h
> new file mode 100644
> index 00000000000..8c4b40dc84c
> --- /dev/null
> +++ b/sys/stand/efi/include/efiuga.h
> @@ -0,0 +1,167 @@
> +/** @file
> + UGA Draw protocol from the EFI 1.1 specification.
> +
> + Abstraction of a very simple graphics device.
> +
> + Copyright (c) 2006 - 2008, Intel Corporation. All rights
> reserved.<BR>
> +
> + This program and the accompanying materials are licensed and made
> available
> + under the terms and conditions of the BSD License which accompanies
> this
> + distribution. The full text of the license may be found at:
> + http://opensource.org/licenses/bsd-license.php
> +
> + THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
> + WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR
> IMPLIED.
> +
> + File name: UgaDraw.h
> +
> +**/
> +
> +#ifndef __UGA_DRAW_H__
> +#define __UGA_DRAW_H__
> +
> +#define EFI_UGA_DRAW_PROTOCOL_GUID \
> + { 0x982c298b, 0xf4fa, 0x41cb, {0xb8, 0x38, 0x77, 0xaa, 0x68, 0x8f,
> 0xb8, 0x39} }
> +
> +typedef struct _EFI_UGA_DRAW_PROTOCOL EFI_UGA_DRAW_PROTOCOL;
> +
> +/**
> + Return the current video mode information.
> +
> + @param This Protocol instance pointer.
> + @param HorizontalResolution Current video horizontal resolution in
> pixels
> + @param VerticalResolution Current video vertical resolution in
> pixels
> + @param ColorDepth Current video color depth in bits per
> pixel
> + @param RefreshRate Current video refresh rate in Hz.
> +
> + @retval EFI_SUCCESS Mode information returned.
> + @retval EFI_NOT_STARTED Video display is not initialized. Call
> SetMode ()
> + @retval EFI_INVALID_PARAMETER One of the input args was NULL.
> +
> +**/
> +typedef
> +EFI_STATUS
> +(EFIAPI *EFI_UGA_DRAW_PROTOCOL_GET_MODE) (
> + IN EFI_UGA_DRAW_PROTOCOL *This,
> + OUT UINT32 *HorizontalResolution,
> + OUT UINT32 *VerticalResolution,
> + OUT UINT32 *ColorDepth,
> + OUT UINT32 *RefreshRate
> + )
> +;
> +
> +/**
> + Return the current video mode information.
> +
> + @param This Protocol instance pointer.
> + @param HorizontalResolution Current video horizontal resolution in
> pixels
> + @param VerticalResolution Current video vertical resolution in
> pixels
> + @param ColorDepth Current video color depth in bits per
> pixel
> + @param RefreshRate Current video refresh rate in Hz.
> +
> + @retval EFI_SUCCESS Mode information returned.
> + @retval EFI_NOT_STARTED Video display is not initialized. Call
> SetMode ()
> +
> +**/
> +typedef
> +EFI_STATUS
> +(EFIAPI *EFI_UGA_DRAW_PROTOCOL_SET_MODE) (
> + IN EFI_UGA_DRAW_PROTOCOL *This,
> + IN UINT32 HorizontalResolution,
> + IN UINT32 VerticalResolution,
> + IN UINT32 ColorDepth,
> + IN UINT32 RefreshRate
> + )
> +;
> +
> +typedef struct {
> + UINT8 Blue;
> + UINT8 Green;
> + UINT8 Red;
> + UINT8 Reserved;
> +} EFI_UGA_PIXEL;
> +
> +typedef union {
> + EFI_UGA_PIXEL Pixel;
> + UINT32 Raw;
> +} EFI_UGA_PIXEL_UNION;
> +
> +typedef enum {
> + EfiUgaVideoFill,
> + EfiUgaVideoToBltBuffer,
> + EfiUgaBltBufferToVideo,
> + EfiUgaVideoToVideo,
> + EfiUgaBltMax
> +} EFI_UGA_BLT_OPERATION;
> +
> +/**
> + Type specifying a pointer to a function to perform an UGA Blt
> operation.
> +
> + The following table defines actions for BltOperations:
> +
> + <B>EfiUgaVideoFill</B> - Write data from the BltBuffer pixel
> (SourceX, SourceY)
> + directly to every pixel of the video display rectangle
> + (DestinationX, DestinationY) (DestinationX + Width, DestinationY
> + Height).
> + Only one pixel will be used from the BltBuffer. Delta is NOT
> used.
> +
> + <B>EfiUgaVideoToBltBuffer</B> - Read data from the video display
> rectangle
> + (SourceX, SourceY) (SourceX + Width, SourceY + Height) and place
> it in
> + the BltBuffer rectangle (DestinationX, DestinationY )
> + (DestinationX + Width, DestinationY + Height). If DestinationX or
> + DestinationY is not zero then Delta must be set to the length in
> bytes
> + of a row in the BltBuffer.
> +
> + <B>EfiUgaBltBufferToVideo</B> - Write data from the BltBuffer
> rectangle
> + (SourceX, SourceY) (SourceX + Width, SourceY + Height) directly
> to the
> + video display rectangle (DestinationX, DestinationY)
> + (DestinationX + Width, DestinationY + Height). If SourceX or
> SourceY is
> + not zero then Delta must be set to the length in bytes of a row
> in the
> + BltBuffer.
> +
> + <B>EfiUgaVideoToVideo</B> - Copy from the video display rectangle
> (SourceX, SourceY)
> + (SourceX + Width, SourceY + Height) .to the video display
> rectangle
> + (DestinationX, DestinationY) (DestinationX + Width, DestinationY
> + Height).
> + The BltBuffer and Delta are not used in this mode.
> +
> +
> + @param[in] This - Protocol instance pointer.
> + @param[in] BltBuffer - Buffer containing data to blit into
> video buffer. This
> + buffer has a size of
> Width*Height*sizeof(EFI_UGA_PIXEL)
> + @param[in] BltOperation - Operation to perform on BlitBuffer and
> video memory
> + @param[in] SourceX - X coordinate of source for the
> BltBuffer.
> + @param[in] SourceY - Y coordinate of source for the
> BltBuffer.
> + @param[in] DestinationX - X coordinate of destination for the
> BltBuffer.
> + @param[in] DestinationY - Y coordinate of destination for the
> BltBuffer.
> + @param[in] Width - Width of rectangle in BltBuffer in
> pixels.
> + @param[in] Height - Hight of rectangle in BltBuffer in
> pixels.
> + @param[in] Delta - OPTIONAL
> +
> + @retval EFI_SUCCESS - The Blt operation completed.
> + @retval EFI_INVALID_PARAMETER - BltOperation is not valid.
> + @retval EFI_DEVICE_ERROR - A hardware error occurred writing
> to the video buffer.
> +
> +--*/
> +typedef
> +EFI_STATUS
> +(EFIAPI *EFI_UGA_DRAW_PROTOCOL_BLT) (
> + IN EFI_UGA_DRAW_PROTOCOL * This,
> + IN EFI_UGA_PIXEL * BltBuffer, OPTIONAL
> + IN EFI_UGA_BLT_OPERATION BltOperation,
> + IN UINTN SourceX,
> + IN UINTN SourceY,
> + IN UINTN DestinationX,
> + IN UINTN DestinationY,
> + IN UINTN Width,
> + IN UINTN Height,
> + IN UINTN Delta OPTIONAL
> + );
> +
> +struct _EFI_UGA_DRAW_PROTOCOL {
> + EFI_UGA_DRAW_PROTOCOL_GET_MODE GetMode;
> + EFI_UGA_DRAW_PROTOCOL_SET_MODE SetMode;
> + EFI_UGA_DRAW_PROTOCOL_BLT Blt;
> +};
> +
> +extern EFI_GUID gEfiUgaDrawProtocolGuid;
> +
> +#endif
> --
> 2.55.0
--
Hugo
efiboot: UGA fb fallback for EFI 1.1