Index: libexec/rc/rc.d/Makefile =================================================================== --- libexec/rc/rc.d/Makefile +++ libexec/rc/rc.d/Makefile @@ -188,6 +188,10 @@ CONFS+= ccd .endif +.if ${MK_EFI} != "no" +CONFS+= uefivars +.endif + .if ${MK_FTP} != "no" CONFS+= ftpd .endif Index: libexec/rc/rc.d/uefivars =================================================================== --- /dev/null +++ libexec/rc/rc.d/uefivars @@ -0,0 +1,89 @@ +#!/bin/sh +# +# Copyright (c) 2003 The FreeBSD Project. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +# SUCH DAMAGE. +# +# $FreeBSD$ +# + +# PROVIDE: uefivars +# REQUIRE: FILESYSTEMS +# KEYWORD: shutdown + +. /etc/rc.subr + +name="uefivars" +desc="Synchronize UEFI with loader.conf variables" +start_cmd="uefivars_start" +stop_cmd="uefivars_start" + +efivar="/usr/sbin/efivar -q" +freebsd_guid='cfee69ad-a0de-47a9-93a8-f63106f8ae99' +iconv="/usr/bin/iconv -t UCS-2-INTERNAL" +loader_conf="/boot/loader.conf" + +loader_variable_defined() +{ + local line_number + line_number=`sed -n -e 's/#.*//' -e "/^${1}=/=" $loader_conf` + echo -n "$line_number" +} + +loader_variable_value() +{ + local value + value=`sed -n -e 's/#.*//' -e "/^${1}=/s/^.*=\"\([^\"]*\)\"/\1/p" $loader_conf` + echo -n "$value" +} + +uefivars_start() +{ + # List of UEFI and loader.conf variables to synchronize. + uefivars_variables='LoaderRotate' + uefivars_config_LoaderRotate='screen.rotate' + + local var loader_var config_value UEFI_value + for var in $uefivars_variables + do + eval loader_var=\$uefivars_config_$var + if [ -z "`loader_variable_defined $loader_var`" ] + then + debug "DELETE LoaderRotate" + # Delete UEFI variables not referenced in loader.conf. + $efivar -D ${freebsd_guid}-${var} + else + # Update UEFI variables that differ from the loader.conf value. + config_value=`loader_variable_value $loader_var` + UEFI_value=`$efivar -p -N -u ${freebsd_guid}-${var}` + if [ "$config_value" != "$UEFI_value" ] + then + debug "UPDATE LoaderRotate $config_value" + echo -n $config_value | $iconv | \ + $efivar -w ${freebsd_guid}-${var} + fi + fi + done +} + +load_rc_config $name +run_rc_command "$1" Index: stand/common/gfx_fb.h =================================================================== --- stand/common/gfx_fb.h +++ stand/common/gfx_fb.h @@ -260,6 +260,9 @@ extern const int cons_to_vga_colors[NCOLORS]; +/* Set by freebsd-LoaderRotate UEFI variable, determines rotation of framebuffer. */ +extern uint32_t rotation; + /* Screen buffer to track changes on the terminal screen. */ extern struct text_pixel *screen_buffer; bool is_same_pixel(struct text_pixel *, struct text_pixel *); Index: stand/common/gfx_fb.c =================================================================== --- stand/common/gfx_fb.c +++ stand/common/gfx_fb.c @@ -773,6 +773,282 @@ } } +#if defined(EFI) +/* + * Rotate a UEFI Blt buffer of pixels by the specified angle (0,90,180,270) clockwise. + * Returns NULL if no rotation is performed, or a pointer to a static buffer + * containing the rotated data. + * We build the rotated buffer with Delta = 0 since there's no way to know what + * it should be if the rotation is 90 or 270 degrees. + */ +static EFI_GRAPHICS_OUTPUT_BLT_PIXEL * +RotateBuffer(EFI_GRAPHICS_OUTPUT_BLT_PIXEL *inbuf, + UINTN angle, UINTN SourceX, UINTN SourceY, + UINTN Width, UINTN Height, + UINTN Delta) +{ + EFI_GRAPHICS_OUTPUT *gop = gfx_state.tg_private; + UINTN srcWidth, destOffset, srcOffset; + static EFI_GRAPHICS_OUTPUT_BLT_PIXEL *outbuf = NULL; + + if (angle != 90 && angle != 180 && angle != 270) + return NULL; + + if (outbuf == NULL && + (outbuf = malloc(gop->Mode->Info->VerticalResolution * + gop->Mode->Info->HorizontalResolution * + sizeof(EFI_GRAPHICS_OUTPUT_BLT_PIXEL))) == NULL) + return NULL; + + srcWidth = Width; + if (Delta != 0) + srcWidth = Delta; + + /* Check for out-of-bounds access. */ + if (srcWidth < SourceX + Width || + srcWidth > gop->Mode->Info->HorizontalResolution || + SourceX + Width > gop->Mode->Info->HorizontalResolution || + SourceY + Height > gop->Mode->Info->VerticalResolution) + return NULL; + + for (UINTN y = 0; y < Height; y++) { + for (UINTN x = 0; x < Width; x++) { + srcOffset = (SourceX + x) + (SourceY + y) * srcWidth; + switch ((int)angle) { + case 0: /* For reference */ + destOffset = (SourceX + x) + (SourceY + y) * Width; + break; + case 90: + destOffset = (SourceY + ((Height - 1) - y)) + + (SourceX + x) * Height; + break; + case 180: + destOffset = (SourceX + ((Width - 1) - x)) + + (SourceY + ((Height - 1) - y)) * Width; + break; + case 270: + destOffset = (SourceY + y) + + (SourceX + ((Width - 1) - x)) * Height; + break; + } + outbuf[destOffset] = inbuf[srcOffset]; + } + } + return (outbuf); +} + +/* Macros to facilitate rotation. */ +#define SWITCH_VALIDATE +#ifdef SWITCH_VALIDATE +#define SWITCH(DIR, AXIS, ORIENT, EXTENT) \ + if (Panel##DIR##AXIS + Rotated##EXTENT > \ + gop->Mode->Info->ORIENT##Resolution - 1) \ + Panel##DIR##AXIS = 0; \ + else \ + Panel##DIR##AXIS = (gop->Mode->Info->ORIENT##Resolution - 1) - \ + (Panel##DIR##AXIS + Rotated##EXTENT) +#define NO_SWITCH(DIR, AXIS, ORIENTATION) \ + if (Panel##DIR##AXIS > gop->Mode->Info->ORIENTATION##Resolution - 1) \ + Panel##DIR##AXIS = gop->Mode->Info->ORIENTATION##Resolution - 1 +#else +#define SWITCH(DIR, AXIS, ORIENT, EXTENT) \ + Panel##DIR##AXIS = (gop->Mode->Info->ORIENT##Resolution - 1) - \ + (Panel##DIR##AXIS + Rotated##EXTENT) +#define NO_SWITCH(DIR, AXIS, ORIENTATION) +#endif +#define SWITCH_SRC_X SWITCH(Src, X, Horizontal, Width) +#define SWITCH_DST_X SWITCH(Dst, X, Horizontal, Width) +#define SWITCH_SRC_Y SWITCH(Src, Y, Vertical, Height) +#define SWITCH_DST_Y SWITCH(Dst, Y, Vertical, Height) +#define NO_SWITCH_SRC_X NO_SWITCH(Src, X, Horizontal) +#define NO_SWITCH_DST_X NO_SWITCH(Dst, X, Horizontal) +#define NO_SWITCH_SRC_Y NO_SWITCH(Src, Y, Vertical) +#define NO_SWITCH_DST_Y NO_SWITCH(Dst, Y, Vertical) + +/* + * Perhaps better implemented as a separate UEFI gop layer (c.f. + * https://github.com/apop2/GopRotate), this rotation layer resides in the + * loader itself, above the UEFI, and will rotate gop->Blt() requests by the + * specified angle. It is more complex than a simple rotation because the + * gop->Blt interface is complex; using the gop direct bitmap interface would be + * simpler but would require more changes to the loader graphics code. + */ +EFI_STATUS +BltRot(EFI_GRAPHICS_OUTPUT_BLT_PIXEL *BltBuffer, + EFI_GRAPHICS_OUTPUT_BLT_OPERATION BltOperation, + UINTN SourceX, UINTN SourceY, + UINTN DestinationX, UINTN DestinationY, + UINTN Width, UINTN Height, + UINTN Delta) +{ + EFI_STATUS status; + EFI_GRAPHICS_OUTPUT *gop = gfx_state.tg_private; + EFI_GRAPHICS_OUTPUT_BLT_PIXEL *PanelBuffer, *unrotatedBuffer; + static EFI_GRAPHICS_OUTPUT_BLT_PIXEL *transformBuffer = NULL; + UINTN PanelSrcX, PanelSrcY, PanelDstX, PanelDstY, RotatedWidth, RotatedHeight; + UINTN destWidth; + + /* + * Translate logical: + * BltBuffer, SourceX, SourceY, DestinationX, DestinationY, Width, Height + * to physical: + * PanelBuffer, PanelSrcX, PanelSrcY, PanelDstX, PanelDstY, RotatedWidth, + * RotatedHeight + * corresponding to the display hardware. + * + * Typically DestinationX = 0, DestinationY= 0 will correspond to the upper left + * pixel of the display in its mounted orientation, whereas PanelDstX = 0, + * PanelDstY = 0 will correspond to the hardware panel 0,0 coordinate. + */ + PanelSrcX = SourceX, PanelSrcY = SourceY; + PanelDstX = DestinationX, PanelDstY = DestinationY; + RotatedWidth = Width, RotatedHeight = Height; + PanelBuffer = BltBuffer; + + switch (BltOperation) { + /* + * Not only do we need to rotate the coordinates, we also need to rotate + * the origin of the BltBuffer transfer box (which itself must be + * rotated). + */ + case EfiBltBufferToVideo: + if (rotation == 0 || + (PanelBuffer = RotateBuffer(BltBuffer, rotation, SourceX, SourceY, + Width, Height, Delta)) == NULL) { + PanelBuffer = BltBuffer; + break; + } + Delta = 0; /* PanelBuffer is built with no delta */ + /* Fallthrough */ + + /* Rotate the coordinates to the panel coordinates. */ + case EfiBltVideoFill: + if (rotation == 90 || rotation == 270) { + PanelDstX = DestinationY, PanelDstY = DestinationX; + RotatedWidth = Height, RotatedHeight = Width; + } + switch (rotation) { + case 0: + break; + case 90: + SWITCH_DST_X; + NO_SWITCH_DST_Y; + break; + case 180: + SWITCH_DST_X; + SWITCH_DST_Y; + break; + case 270: + NO_SWITCH_DST_X; + SWITCH_DST_Y; + break; + } + break; + + /* Rotate the coordinates to and from the panel coordinates. */ + case EfiBltVideoToVideo: + if (rotation == 90 || rotation == 270) { + PanelSrcX = SourceY, PanelSrcY = SourceX; + PanelDstX = DestinationY, PanelDstY = DestinationX; + RotatedWidth = Height, RotatedHeight = Width; + } + switch (rotation) { + case 90: + SWITCH_SRC_X; + SWITCH_DST_X; + NO_SWITCH_SRC_Y; + NO_SWITCH_DST_Y; + break; + case 180: + SWITCH_SRC_X; + SWITCH_DST_X; + SWITCH_SRC_Y; + SWITCH_DST_Y; + break; + case 270: + NO_SWITCH_SRC_X; + NO_SWITCH_DST_X; + SWITCH_SRC_Y; + SWITCH_DST_Y; + break; + } + break; + + /* Rotate the coordinates from the panel coordinates. */ + case EfiBltVideoToBltBuffer: + if (rotation == 0 || + (transformBuffer == NULL && + (transformBuffer = malloc(gop->Mode->Info->HorizontalResolution * + gop->Mode->Info->VerticalResolution * + sizeof(EFI_GRAPHICS_OUTPUT_BLT_PIXEL))) == NULL)) + break; + + PanelBuffer = transformBuffer; + if (rotation == 90 || rotation == 270) { + PanelSrcX = SourceY, PanelSrcY = SourceX; + RotatedWidth = Height, RotatedHeight = Width; + } + switch (rotation) { + case 90: + SWITCH_SRC_X; + NO_SWITCH_SRC_Y; + break; + case 180: + SWITCH_SRC_X; + SWITCH_SRC_Y; + break; + case 270: + NO_SWITCH_SRC_X; + SWITCH_SRC_Y; + break; + } + break; + } + + status = gop->Blt(gop, PanelBuffer, BltOperation, + PanelSrcX, PanelSrcY, PanelDstX, PanelDstY, + RotatedWidth, RotatedHeight, Delta); + if (EFI_ERROR(status)) + return (status); + + /* Unless we need to unrotate and merge data into BltBuffer, we are done. */ + if (BltOperation != EfiBltVideoToBltBuffer || PanelBuffer == BltBuffer) + return (status); + + /* + * Until this point, on rotation failure we simply pass the data + * unrotated. From here on, we return an error (which will likely cause + * loader failure). + */ + unrotatedBuffer = RotateBuffer(PanelBuffer, 360 - rotation, + DestinationX, DestinationY, Width, Height, Delta); + if (unrotatedBuffer == NULL) + return (EFI_OUT_OF_RESOURCES); + + /* + * It is not obvious what the correct reconstruction of a non-zero delta + * should be. This is likely to work correctly. + */ + destWidth = Width; + if (Delta != 0) + destWidth = Delta; + + /* Check for out-of-bounds access. */ + if (destWidth < DestinationX + Width || + destWidth > gop->Mode->Info->HorizontalResolution || + DestinationX + Width > gop->Mode->Info->HorizontalResolution || + DestinationY + Height > gop->Mode->Info->VerticalResolution) + return (EFI_INVALID_PARAMETER); + + for (UINTN y = DestinationY; y < DestinationY + Height; y++) { + for (UINTN x = DestinationX; x < DestinationX + Width; x++) { + BltBuffer[x + y * destWidth] = unrotatedBuffer[x + y * Width]; + } + } + return (status); +} +#endif + int gfxfb_blt(void *BltBuffer, GFXFB_BLT_OPERATION BltOperation, uint32_t SourceX, uint32_t SourceY, @@ -794,28 +1070,26 @@ tpl = BS->RaiseTPL(TPL_NOTIFY); switch (BltOperation) { case GfxFbBltVideoFill: - gfxfb_shadow_fill(BltBuffer, DestinationX, - DestinationY, Width, Height); - status = gop->Blt(gop, BltBuffer, EfiBltVideoFill, + status = BltRot(BltBuffer, EfiBltVideoFill, SourceX, SourceY, DestinationX, DestinationY, Width, Height, Delta); break; case GfxFbBltVideoToBltBuffer: - status = gop->Blt(gop, BltBuffer, + status = BltRot(BltBuffer, EfiBltVideoToBltBuffer, SourceX, SourceY, DestinationX, DestinationY, Width, Height, Delta); break; case GfxFbBltBufferToVideo: - status = gop->Blt(gop, BltBuffer, EfiBltBufferToVideo, + status = BltRot(BltBuffer, EfiBltBufferToVideo, SourceX, SourceY, DestinationX, DestinationY, Width, Height, Delta); break; case GfxFbBltVideoToVideo: - status = gop->Blt(gop, BltBuffer, EfiBltVideoToVideo, + status = BltRot(BltBuffer, EfiBltVideoToVideo, SourceX, SourceY, DestinationX, DestinationY, Width, Height, Delta); break; Index: stand/efi/libefi/env.c =================================================================== --- stand/efi/libefi/env.c +++ stand/efi/libefi/env.c @@ -469,7 +469,7 @@ /* * Print FreeBSD variables. - * We have LoaderPath and LoaderDev as CHAR16 strings. + * We have LoaderRotate, LoaderPath and LoaderDev as CHAR16 strings. */ static int efi_print_freebsd(const CHAR16 *varnamearg, uint8_t *data, @@ -481,7 +481,8 @@ if (ucs2_to_utf8(varnamearg, &var) != 0) return (CMD_ERROR); - if (strcmp("LoaderPath", var) == 0 || + if (strcmp("LoaderRotate", var) == 0 || + strcmp("LoaderPath", var) == 0 || strcmp("LoaderDev", var) == 0) { printf(" = "); printf("%S", (CHAR16 *)data); Index: stand/efi/loader/bootinfo.c =================================================================== --- stand/efi/loader/bootinfo.c +++ stand/efi/loader/bootinfo.c @@ -304,8 +304,17 @@ efifb.fb_addr = gfx_state.tg_fb.fb_addr; efifb.fb_size = gfx_state.tg_fb.fb_size; - efifb.fb_height = gfx_state.tg_fb.fb_height; - efifb.fb_width = gfx_state.tg_fb.fb_width; + /* + * If we have rotated the framebuffer height and width, rotate them back + * before passing them to the kernel + */ + if (rotation == 90 || rotation == 270) { + efifb.fb_height = gfx_state.tg_fb.fb_width; + efifb.fb_width = gfx_state.tg_fb.fb_height; + } else { + efifb.fb_height = gfx_state.tg_fb.fb_height; + efifb.fb_width = gfx_state.tg_fb.fb_width; + } efifb.fb_stride = gfx_state.tg_fb.fb_stride; efifb.fb_mask_red = gfx_state.tg_fb.fb_mask_red; efifb.fb_mask_green = gfx_state.tg_fb.fb_mask_green; Index: stand/efi/loader/framebuffer.c =================================================================== --- stand/efi/loader/framebuffer.c +++ stand/efi/loader/framebuffer.c @@ -43,7 +43,9 @@ #include #include "bootstrap.h" +#include "efichar.h" #include "framebuffer.h" +#include "gfx_fb.h" static EFI_GUID conout_guid = EFI_CONSOLE_OUT_DEVICE_GUID; EFI_GUID gop_guid = EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID; @@ -56,6 +58,9 @@ /* Cached EDID. */ struct vesa_edid_info *edid_info = NULL; +/* Angle to rotate boot console display. */ +uint32_t rotation = 0; + static EFI_GRAPHICS_OUTPUT *gop; static EFI_UGA_DRAW_PROTOCOL *uga; @@ -147,11 +152,42 @@ EFI_GRAPHICS_OUTPUT_MODE_INFORMATION *info) { int result; + size_t len = 0; + efi_char *buf; + char *angle = NULL; + EFI_STATUS rv; + + /* + * Use the UEFI variable freebsd-LoaderRotate to rotate the framebuffer + * to match the panel orientation. We use a UEFI variable because + * the framebuffer is set up very early in the boot process, before + * loader variables (even the early /boot/efi/EFI/FreeBSD/loader.env + * variables) are set up. + * + * It is intended that an rc.d script shall use the screen.rotate loader + * variable to update freebsd-LoaderRotate for the next boot. + */ + if (efi_freebsd_getenv("LoaderRotate", NULL, &len) == EFI_BUFFER_TOO_SMALL) + if ((buf = calloc(len + 1, sizeof(efi_char)))) { + if (efi_freebsd_getenv("LoaderRotate", buf, &len) == EFI_SUCCESS) + if (ucs2_to_utf8(buf, &angle) == 0) { + rotation = strtol(angle, NULL, 0); + free(angle); + } + free(buf); + } + if (rotation != 90 && rotation != 180 && rotation != 270) + rotation = 0; efifb->fb_addr = mode->FrameBufferBase; efifb->fb_size = mode->FrameBufferSize; - efifb->fb_height = info->VerticalResolution; - efifb->fb_width = info->HorizontalResolution; + if (rotation == 90 || rotation == 270) { + efifb->fb_width = info->VerticalResolution; + efifb->fb_height = info->HorizontalResolution; + } else { + 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);