Translate

Archives

Problems Testing UEFI APIs Using QEMU OVMF and GNU-EFI

Recently there has been interest in using the UEFI EDK2 OVMF package with QEMU to demonstrate UEFI Secure Boot functionality. See James Bottomley, Peter Jones, Jeremy Kerr’s sbsigntool et al. If you are unfamiliar with Secure Boot, I suggest you start by reading the following article about Secure Boot which is on linux.com.

So how complete is the support for UEFI APIs and non volatile (NV) variables in the current version of the EDK2 OVMF? Well, it turns out that there are actually two distinct problems. The first is with NV variables and the second – surprisingly – is with the contents of the EFI header files which are provided on modern Linux distributions.

Unfortunately, neither EDK2 DUET or OVMF provide proper NV variable support. In the case of OVMF, the Firmware Volume Block (FVB) services emulate non-volatile variable storage by pretending that a memory buffer is storage for the NV variables. See …/OvmfPkg/EmuVariableFvbRuntimeDxe/Fvb.c. In the case of DUET, an actual file called Efivar.bin is created. See …/DuetPkg/FvbRuntimeService/FWBlockService.c and ../DuetPkg/FvbRuntimeService/FileIo.c

Consider the following simple UEFI application:

#include <efi.h>
#include <efilib.h>

EFI_STATUS
efi_main (EFI_HANDLE image, EFI_SYSTEM_TABLE *systab)
{
    EFI_STATUS rc = EFI_SUCCESS;
    UINT32 Attr;
    UINT64 MaxStoreSize = 0;
    UINT64 RemainStoreSize = 0;
    UINT64 MaxSize = 0;

    InitializeLib(image, systab);

    Attr = EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_HARDWARE_ERROR_RECORD;

    rc = uefi_call_wrapper(RT->QueryVariableInfo, 4,
                           Attr, &MaxStoreSize, &RemainStoreSize, &MaxSize);

    if (rc != EFI_SUCCESS) {
        Print(L"ERROR: Failed to get store sizes: %d\n", rc);
    } else {
        Print(L"Max Storage Size: %ld\n", MaxStoreSize);
        Print(L"Remaining Storage Size: %ld\n", RemainStoreSize);
        Print(L"Max Variable Size: %ld\n", MaxSize);
    }

    return rc;
}


and its associated Makefile:

SRCDIR     = .
PREFIX     := /usr
HOSTARCH   = $(shell uname -m | sed s,i[3456789]86,ia32,)
ARCH       := $(shell uname -m | sed s,i[3456789]86,ia32,)
INCDIR     = -I.
CPPFLAGS   = -DCONFIG_$(ARCH)
CFLAGS     = $(ARCH3264) -g -O0 -fpic -Wall -fshort-wchar -fno-strict-aliasing -fno-merge-constants --std=gnu99 -D_GNU_SOURCE
ASFLAGS    = $(ARCH3264)
LDFLAGS    = -nostdlib
INSTALL    = install

CC         = gcc
AS         = as
LD         = ld.bfd
AR         = ar
RANLIB     = ranlib
OBJCOPY    = objcopy

ifeq ($(ARCH), ia32)
  LIBDIR := $(PREFIX)/lib
  ifeq ($(HOSTARCH), x86_64)
    ARCH3264 := -m32
  endif
endif

ifeq ($(ARCH), x86_64)
  CFLAGS += -mno-red-zone
  LIBDIR := $(PREFIX)/lib64
  ifeq ($(HOSTARCH), ia32)
    ARCH3264 := -m64
  endif
endif

FORMAT=efi-app-$(HOSTARCH)
LDFLAGS = -nostdlib -T $(LIBDIR)/gnuefi/elf_$(HOSTARCH)_efi.lds -shared -Bsymbolic $(LIBDIR)/gnuefi/crt0-efi-$(HOSTARCH).o -L$(LIBDIR)
LIBS=-lefi -lgnuefi $(shell $(CC) -print-libgcc-file-name)
CCLDFLAGS =
CFLAGS = -I/usr/include/efi/ -I/usr/include/efi/$(HOSTARCH)/ -I/usr/include/efi/protocol -fpic -fshort-wchar -fno-reorder-functions -fno-strict-aliasing -fno-merge-constants -mno-red-zone -Wimplicit-function-declaration


TARGETS = qv.efi

all : $(TARGETS)

clean : 
        @rm -rf *.o *.a *.so $(TARGETS)

.PHONY: all clean install

.PHONY: all clean install


%.efi : %.so
        $(OBJCOPY) -j .text -j .sdata -j .data -j .dynamic -j .dynsym -j .rel \
                -j .rela -j .reloc --target=$(FORMAT) $*.so $@

%.so: %.o
        $(LD) $(LDFLAGS) -o $@ $^ $(LIBS)

%.o: %.c
        $(CC) $(INCDIR) $(CFLAGS) $(CPPFLAGS) -D__UEFI__ -c $< -o $@

%.S: %.c
        $(CC) $(INCDIR) $(CFLAGS) $(CPPFLAGS) -D__UEFI__ -S $< -o $@

%.E: %.c
        $(CC) $(INCDIR) $(CFLAGS) $(CPPFLAGS) -D__UEFI__ -E $< -o $@

The QueryVariableInfo is an EFI Runtime Service which was added to the UEFI 2.0 Specification way back in October 2008. The purpose of this protocol is to return information about the EFI variable store. Specifically, the function allows a caller to obtain the information about the maximum size of the storage space available for the EFI variables, the remaining size of the storage space available for the EFI variables and the maximum size of each individual EFI variable, associated with the attributes specified.

So where is QueryVariableInfo used in the real world? Well, hardware error records on UEFI platforms for one. Hardware error records are used to persist information about kernel oops and the like. Such data is stored in a format called a UEFI Common Platform Error Record (CPER.) For more information about CPER, read the UEFI Specification, Appendix N. To determine the amount of space in bytes guaranteed by the platform to be available for saving such hardware error records, the OS invokes QueryVariableInfo, setting the EFI_VARIABLE_HARDWARE_ERROR_RECORD bit in the Attributes bitmask.

The first problem you will encounter if you try to compile this application on Fedora 17, or a similar modern Linux distribution that uses the gnu-efi package, is that QueryVariableInfo is not included in <efi/efiapi.h> which is the header that defines the EFI Runtime Services. Similarly, EFI_VARIABLE_HARDWARE_ERROR_RECORD is undefined.

Examining the EFI header files under /usr/include/efi revealed that the header file definitions have not been updated in many years. In fact, they are for EFI firmware revision 12.33, a very old version.

#define EFI_FIRMWARE_MAJOR_REVISION 12
#define EFI_FIRMWARE_MINOR_REVISION 33


The current EFI firmware version is 16.1. This is what the EDK2 expects.

#define EFI_FIRMWARE_MAJOR_REVISION 0x1000
#define EFI_FIRMWARE_MINOR_REVISION 1 


These headers come from the gnu-efi package, whose maintainer is Nigel Croxon (still with HP?), and are in need of serious updating.

Here are the diffs that you need to apply to the GNU-EFI headers in order for the above application to compile correctly:

$ diff /usr/include/efi/efi.h /usr/include/efi/efi.h.org
45d44
< #include "eficapsule.h"
$ 

$ diff /usr/include/efi/efiapi.h /usr/include/efi/efiapi.h.org
23d22
< 
217,218d215
< #define	EFI_VARIABLE_HARDWARE_ERROR_RECORD   0x00000008
< #define	EFI_VARIABLE_AUTHENTICATED_WRITE_ACCESS   0x00000010
232a230
> 
587,613d584
< typedef
< EFI_STATUS
< (EFIAPI *EFI_UPDATE_CAPSULE) (
<   IN EFI_CAPSULE_HEADER   **CapsuleHeaderArray,
<   IN UINTN                CapsuleCount,
<   IN EFI_PHYSICAL_ADDRESS ScatterGatherList OPTIONAL
<   );
< 
< typedef
< EFI_STATUS
< (EFIAPI *EFI_QUERY_CAPSULE_CAPABILITIES) (
<   IN  EFI_CAPSULE_HEADER **CapsuleHeaderArray,
<   IN  UINTN              CapsuleCount,
<   OUT UINT64             *MaximumCapsuleSize,
<   OUT EFI_RESET_TYPE     *ResetType
<   );
< 
< typedef
< EFI_STATUS
< (EFIAPI *EFI_QUERY_VARIABLE_INFO) (
<   IN  UINT32 Attributes,
<   OUT UINT64 *MaximumVariableStorageSize,
<   OUT UINT64 *RemainingVariableStorageSize,
<   OUT UINT64 *MaximumVariableSize
<   );
< 
< 
748,750c719
<     EFI_UPDATE_CAPSULE              UpdateCapsule;
<     EFI_QUERY_CAPSULE_CAPABILITIES  QueryCapsuleCapabilities;
<     EFI_QUERY_VARIABLE_INFO         QueryVariableInfo;
---
> 
$

$ cat /usr/include/efi/eficapsule.h
#ifndef _EFI_CAPSULE_H_
#define _EFI_CAPSULE_H_

// An array of these describe the blocks that make up a capsule for a capsule update.
typedef struct {
  UINT64                Length;     // length of the data block
  EFI_PHYSICAL_ADDRESS  Data;       // physical address of the data block
  UINT32                Signature;  // CBDS
  UINT32                CheckSum;   // to sum this structure to 0
} EFI_CAPSULE_BLOCK_DESCRIPTOR;

#define CAPSULE_BLOCK_DESCRIPTOR_SIGNATURE  EFI_SIGNATURE_32 ('C', 'B', 'D', 'S')

typedef struct {
  EFI_GUID  CapsuleGuid;
  UINT32    HeaderSize;
  UINT32    Flags;
  UINT32    CapsuleImageSize;
} UEFI_CAPSULE_HEADER;

typedef struct {
  UINT32   CapsuleArrayNumber;
  VOID*    CapsulePtr[1];
} EFI_CAPSULE_TABLE;

#endif

// Bits in the flags field of the capsule header
#define EFI_CAPSULE_HEADER_FLAG_SETUP 0x00000001  // supports setup changes

// This is the GUID of the capsule header of the image on disk.
#define EFI_CAPSULE_GUID \
  { \
    0x3B6686BD, 0x0D76, 0x4030, 0xB7, 0x0E, 0xB5, 0x51, 0x9E, 0x2F, 0xC5, 0xA0 \
  }

// This is the GUID of the file created by the capsule application that contains the path to the device(s) to update.
#define EFI_PATH_FILE_NAME_GUID \
  { \
    0x7644C181, 0xFA6E, 0x46DA, 0x80, 0xCB, 0x04, 0xB9, 0x90, 0x40, 0x62, 0xE8 \
  }

// This is the GUID of the configuration results file created by the capsule application.
#define EFI_CONFIG_FILE_NAME_GUID \
  { \
    0x98B8D59B, 0xE8BA, 0x48EE, 0x98, 0xDD, 0xC2, 0x95, 0x39, 0x2F, 0x1E, 0xDB \
  }

#endif // #ifndef _EFI_CAPSULE_H_


Note that if you run this application on the current UEFI QEMU OVMF setup, the three values returned are all zero. This is because of the OVMF NV storage in memory hack. However, if you run the application on actual UEFI 2.3.1 firmware, you will get proper valid values.

Keep experimenting!

3 comments to Problems Testing UEFI APIs Using QEMU OVMF and GNU-EFI

  • jlee

    Thanks for your article, it’s useful to me for query the store size of variable.

    But there have 2 statements need fix in eficapsule.h on this webpage. Add the following diff for note:

    diff –git a/inc/eficapsule.h b/inc/eficapsule.h
    index 2614b9f..a4d0ccf 100644
    — a/inc/eficapsule.h
    +++ b/inc/eficapsule.h
    @@ -16,15 +16,13 @@ EFI_GUID CapsuleGuid;
    UINT32 HeaderSize;
    UINT32 Flags;
    UINT32 CapsuleImageSize;
    -} UEFI_CAPSULE_HEADER;
    +} EFI_CAPSULE_HEADER;

    typedef struct {
    UINT32 CapsuleArrayNumber;
    VOID* CapsulePtr[1];
    } EFI_CAPSULE_TABLE;

    -#endif

    // Bits in the flags field of the capsule header
    #define EFI_CAPSULE_HEADER_FLAG_SETUP 0x00000001 // supports setup changes

  • AJay

    Hi
    Is windows 8 providing any exposes to efi runtime services like updatecapsule for the firmware update of devices.