From: "Hugo Osvaldo Barrera" Subject: Re: efiboot: UGA fb fallback for EFI 1.1 To: tech@openbsd.org Date: Wed, 23 Sep 2026 13:04:14 +0200 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 > #include > #include > +#include > > #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.
> + > + 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: > + > + EfiUgaVideoFill - 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. > + > + EfiUgaVideoToBltBuffer - 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. > + > + EfiUgaBltBufferToVideo - 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. > + > + EfiUgaVideoToVideo - 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