Index | Thread | Search

From:
"Hugo Osvaldo Barrera" <hugo@whynothugo.nl>
Subject:
Re: efiboot: UGA fb fallback for EFI 1.1
To:
tech@openbsd.org
Date:
Wed, 23 Sep 2026 13:04:14 +0200

Download raw body.

Thread
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