Showing posts with label UEFI. Show all posts
Showing posts with label UEFI. Show all posts

Tuesday, July 4, 2017

Porting UEFI to XXX, step 1

I've decided to do the actual blogging for this project *in* the repo itself. See https://github.com/andreiw/ppcnw-edk2/blob/master/README.md. After all, markdown is convenient enough and using Blogger on the G4 is p-a-i-n-f-u-l.

So it turns out that blogging about something after the fact is pretty tough. I really wanted to blog about my PoC port of UEFI to the OpenPower ecosystem, but it's incredibly difficult to go back and try to systematize something that's been a few years back.

So let's try this again. This time, our victim will be a G4 12" PowerBook6,8 with a 7447A. That's a 32-bit PowerPC. Now, I'll go in small steps and document *everything*. For added fun, we'll begin porting on the target itself, at least until that gets too tedious.

First, I updated to the latest (and last) Debian 8 (Jessie).

Now let's clone the tree.

$ git clone https://github.com/tianocore/edk2

Setup the UEFI environment.

$ cd edk2
$ . edksetup.sh

Now we need to get the BaseTools building.

pbg4:~/src/edk2/BaseTools/ make
make -C Source/C
make[1]: Entering directory '/home/andreiw/src/edk2/BaseTools/Source/C'
Attempting to detect ARCH from 'uname -m': ppc
Could not detected ARCH from uname results
GNUmakefile:36: *** ARCH is not defined!.  Stop.
make[1]: Leaving directory '/home/andreiw/src/edk2/BaseTools/Source/C'
GNUmakefile:25: recipe for target 'Source/C' failed
make: *** [Source/C] Error 2

Ok. Let's fix that. We'll first need a Source/C/Include/PPC/ProcessorBind.h file.

ProcessorBind.h I've derived from another 32-bit CPU, like IA32 or ARM. This contains type definitions, mostly. It's boilerplate. In case there are multiple coding conventions for your architectures and it's not obvious which one you should be using, you might wish to specify what the EFIAPI attribute will be. Like, on x86 Windows-style cdecl is used, regardless of how you build the rest of Tiano. On most architectures an empty define is fine.

Now appropriately hook it into Source/C/Makefiles/header.makefile.

--- a/BaseTools/Source/C/Makefiles/header.makefile
+++ b/BaseTools/Source/C/Makefiles/header.makefile
@@ -43,6 +43,10 @@ ifeq ($(ARCH), AARCH64)
 ARCH_INCLUDE = -I $(MAKEROOT)/Include/AArch64/
 endif
+ifeq ($(ARCH), PPC)
+ARCH_INCLUDE = -I $(MAKEROOT)/Include/PPC/
+endif

Fix the ARCH detection in Source/C/GNUmakefile.

--- a/BaseTools/Source/C/GNUmakefile
+++ b/BaseTools/Source/C/GNUmakefile
@@ -31,6 +31,9 @@ ifndef ARCH
   ifneq (,$(findstring arm,$(uname_m)))
     ARCH=ARM
   endif
  ifneq (,$(findstring ppc,$(uname_m)))
    ARCH=PPC
  endif

Ok, ensure you have the libuuid headers (Debian uuid-dev) and g++. And...

You are done. This gives you the tools need to help build UEFI. Now we need to teach the build system about PowerPC...

Saturday, May 7, 2016

Porting TianoCore to a new architecture

"UEFI" on...?

This article is the first in a series of posts touching on the general process of bringing up TianoCore EDK2 on an otherwise unsupported architecture. Maybe you want to support UEFI for your CPU architecture, or simply have a reasonable firmware environment.  In either case, because UEFI is not actually defined for your architecture, you're going to have to do a bit more work than your typical platform bring-up. By the time you're done, you could become a perfect addition to the UEFI forum and its specification committees...yeah!

This blog post and the ones following it continually refer to the ongoing PPC64LE Tiano port I am working on, available at https://github.com/andreiw/ppc64le-edk2/. Since everyone can read the fine code, this document mostly highlights the various steps performed throughout the commits. The git repo isn't perfect, though. Some changes ended up evolving over a few commits while I ironed things out and brought in more code. Hopefully I don't miss mentioning anything important.


TianoCore

Tiano is Intel's open-source (BSD-licensed) implementation of the UEFI specification. EDK2 is the second and current iteration of the implementation.

UEFI officially is supported on IA32, X64, IPF, ARM and AARCH64 architectures, the EDK2 has CPU support code for the x86 and ARM variants. There's a MIPS EDK1 port floating about, and now two PPC64LE OpenPower EDK2 ports, one of which ended up fueling this article...

I'm not going to focus on the architecture of either UEFI or Tiano. Good books have been written on the subject. Here's some tl;dr material, though, for the hopelessly impatient:
At least read the User Documentation and glance at the boot flow diagram. You should be now able to fetch, build and boot Tiano Core using the emulation package and have a rough understanding of what it takes to get a build going via Conf/target.txt and Conf/tools_def.txt.

Your Target

Your target is a 32-bit or 64-bit little-endian chip. I suppose big-endian is doable, but none of the Tiano code is endian safe and UEFI is strictly little-endian.

Development Environment

This assumes that you are doing development on Linux and are using and ELF toolchain and GCC compilers. You are going to need:

Basic Project Setup

Pick a short identifier for your architecture. Pick a name that's unused - for the Power8 port I picked PPC64. This tag will be used with the Tiano build scripts. The next step is to create a couple of Pkg directories that will contain our stuff. In my PPC64 port I initially went with a single-package solution, but I should be working on splitting it up into the more conventional layout, where platform-independent portions are in PPC64Pkg and platform-dependent parts (including build scripts for building for actual boards) are in PPC64PlatformPkg. You can refer to this commit as an example for the minimum required to build a dummy EFI application that does nothing.

If our architecture was supported, then running a similar build command for your package would succeed.
build -p YourArchPlatformPkg/YourArchPlatformPkg.dsc

Build Infrastructure

The next step is to enable the build infrastructure and scripts to understand your architecture identifier. Here's a list of files I had to modify - this is all stuff under BaseTools/Source/Python and the changes are incredibly mechanical.
  • Common/DataType.py
  • Common/EdkIIWorkspaceBuild.py
  • Common/FdfParserLite.py
  • Common/MigrationUtilities.py
  • CommonDataClass/CommonClass.py
  • CommonDataClass/ModuleClass.py
  • CommonDataClass/PlatformClass.py
  • GenFds/FdfParser.py
  • GenFds/FfsInfStatement.py
  • GenFds/GenFds.py
  • TargetTool/TargetTool.py
  • build/build.py

Build tools

UEFI executables are PE/COFF files. Since we are building on Linux, EDK2 uses a workflow where ELF artifacts produced by the cross-compiler are converted into PE32/PE32+ files with the GenFw tool. The PE/COFF artifacts are then wrapped into an FFS object and assembled into what is known as an FV ("firmware volume"). Multiple FVs are put into an FD.  The FV is really a flat file system that uses GUIDs for everything and can store other types of objects as well. You could also generate what is known as a TE (terse executable), but it's basically a cut down version of COFF FWIW.

Tiano deals with several kinds of executables. The UEFI runtime (DXE core, UEFI drivers, and so on) are relocated as they are loaded, while the code that runs prior to the UEFI runtime itself is XIP (execute-in-place) and is thus pre-relocated to fixed addresses by the tool constructing the FV. The point behind this being that such pre-UEFI code (which is the SEC and PEI phases for Tiano), is run in an environment before the DRAM is available.

Thus we need to enable GenFw to create PE/COFF executables from ELF for our architecture. This ties into the compiler options we're going to use, which is not something we've addressed yet. It helps to understand that a PE/COFF file is basically a position-independent executable. Although there is "preferred linking address" that all symbols are relocated against, the PE/COFF image contains a sufficient amount of relocation information, known as "base relocations", to allow loading at any address. So it would appear, that the easiest approach is to generate a position-independent ET_DYN ELF executable (with the -pie flag to the linker) and then focus on converting the output to COFF. This is the approach I highly suggest adopting. You will only have to deal with a single relocation type (R_PPC64_RELATIVE in my case) that will map naturally to either the 64-bit or the 32-bit COFF base relocation type, depending on the bit width of your architecture.

Other approaches are possible, such as embedding all relocations with the --emit-relocs linker flag and dealing with the entire soup of crazy relocs later, but the success of this approach is highly dependent on the architecture and ABI. It may be impossible to convert to PE/COFF due to a mismatch between ELF and COFF base relocs and ABI issues. When first working on the PPC64 port, I first followed the AArch64 approach that did just this, and ended up being forced to use the older (and not really meant for LE) ELFv1 ABI. Don't do it.

Note that depending on the ABI, you may have to do a bit of tool work. I am guessing this was the reason why the AArch64 port never adopted using PIE ELF binaries. Fortunately, you should be able to follow along my changes to Elf64Convert.c. You may also have to make changes to the base linker script used with the GNU toolchains.

Don't forget that you will need to manually rebuild the BaseTools if you make any changes!
make -C BaseTools

At this point we can go back and figure out the compile options. This is the BaseTools/Conf/tools_def.template file, that is then copied to Conf/ by edksetup.sh on freshly checked-out trees. The compiler options heavily depend on your architecture, of course, but generally speaking:
  • build on top of definitions made for new architectures like AArch64, because there's simply less of them in this file to wrap your mind around
  • consider the PPC64 definitions in my tree
  • -pie, unless position-independent executables don't work for you for some reason
  • large model
  • soft float (you can always move to hard float later if that rocks your boat, but it's just more CPU state to wrap your head around)
  • PECOFF_HEADER_SIZE=0x228 for 64-bit chips, 0x220 for 32-bit ones
This is the point where trying to build again should start giving you compile errors, because we still haven't modified any of the Tiano include and library files to be aware of the new architecture.

To be continued.

Wednesday, May 25, 2011

Using GDB to debug UEFI.

GDB, the GNU debugger, is a natural choice for debugging TianoCore EDK2 firmware. In the context of playing around with OVMF (the virtual machine EDK2 target) on QEMU or Xen, you can use the QEMU/Xen debugging support.

On QEMU that means passing the '-s' option. For OVMF that command to invoke would be something like -
fjnh84@fjnh84-desktop:~/mine/edk2/OvmfPkg$ ./build.sh -A IA32 qemu -s
Initializing workspace
/home/fjnh84/mine/edk2/BaseTools
Loading previous configuration from $WORKSPACE/Conf/BuildEnv.sh
WORKSPACE: /home/fjnh84/mine/edk2
EDK_TOOLS_PATH: /home/fjnh84/mine/edk2/BaseTools
using prebuilt tools
Running: qemu -L /home/fjnh84/mine/edk2/Build/OvmfIa32/DEBUG_GCC44/QEMU -hda fat:/home/fjnh84/mine/edk2/Build/OvmfIa32/DEBUG_GCC44/IA32 -s
Could not open option rom 'vapic.bin': No such file or directory
pci_add_option_rom: failed to find romfile "pxe-rtl8139.bin"
For Xen it means running the 'gdbsx' tool, something like -
fjnh84@fjnh84-desktop:~/mine/edk2/OvmfPkg$ gdbsx -a domid 32 1234
In either case, then you would attach a debugger with something like -
(gdb) target remote localhost:1234
As per my earlier post you would then reload UEFI symbols, etc.

Then you start running into problems.
(gdb) print gST
$1 = (EFI_SYSTEM_TABLE **) 0

First of all, the values of .bss globals are bogus. If you look at the address of the symbols, it ends up being the unrelocated value relative to the VMA of the symbol file.

Haven't quite root caused the issue (.bss variables don't seem to be adjusted for the actual objfile location), but a simple workaround is to always link with an ld script that forces all sections to go to .text in the output. Watch out for the the COMMON variables too, the existing script doesn't take care of that.

Since I use the GCC44 toolchain, I modified $(EDK_TOOLS_PATH)/Scripts/gcc4.4-ld-script to be like -
SECTIONS
{
 /* . = 0 + SIZEOF_HEADERS; */
 . = 0x280;
 .text ALIGN(0x20) :
 {
   *(.text .stub .text.* .gnu.linkonce.t.*)
   . = ALIGN(0x20);
   *(.rodata .rodata.* .gnu.linkonce.r.*)
   *(.data .data.* .gnu.linkonce.d.*)
   . = ALIGN(0x20);
   *(.bss .bss.* COMMON)
   . = ALIGN(0x20);
   *(.got .got.*)
   . = ALIGN(0x20);
   *(.rela .rela.*)
   . = ALIGN(0x20);
 } =0x90909090
 /DISCARD/ : {
   *(.note.GNU-stack) *(.gnu_debuglink)
   *(.interp)
   *(.dynsym)
   *(.dynstr)
   *(.dynamic)
   *(.hash)
   *(.comment)
 }

And of course fixed my tools_def.txt like this for IA32 -
DEFINE GCC44_IA32_X64_DLINK_FLAGS    = DEF(GCC44_IA32_X64_DLINK_COMMON) --entry \
$(IMAGE_ENTRY_POINT) -u $(IMAGE_ENTRY_POINT) -Map $(DEST_DIR_DEBUG)/$(BASE_NAME).map \
--script=$(EDK_TOOLS_PATH)/Scripts/gcc4.4-ld-script

Additionally, whatever frame you select prior to dumping the address 'gST', you will always get the same value. Problem is, all UEFI code lives in the same address space, but the different drivers are separate programs. GDB expects globals to be unique, and assumes all loaded symbols are part of the same program. When a global is looked up, gdb scans all partial symtabs starting from the first known objfile, which is the first file that was added using 'add-symbol-file', thus it is a first fit type of deal.

I poked around GDB a bit (7.1, a bit old, but w/e), and decided it was reasonably easy to teach GDB the concept of 'symbol file scope', something often referred to as 'module scope' in other debuggers. Ultimately, that meant -
  • Looking in objfile containing current scope block before looking at all objfiles.
  • Modifying lookup_symtab code to also handle symbol file names (only handled source names before).
  • Fixing location completion so auto-complete works correctly for symbol file names.
  • Adding a command to dump loaded symbol files.
  • Modify above command to also dump modules matching a particular source file name.
Whereas before GDB had file scope, e.g.-
(gdb) print 'BdsConnect.c'::BdsLibConnectAllDriversToAllControllers
$11 = {void (void)} 0x17eb2619 <BdsLibConnectAllDriversToAllControllers>
Now you also get symbol file scope -
(gdb) print 'BdsDxe.dll'::BdsLibConnectAllDriversToAllControllers
$12 = {void (void)} 0x17eb2619 <BdsLibConnectAllDriversToAllControllers>
The 'list-symbol-files' command is useful too -
(gdb) list-symbol-files DiskIo.c Dispatcher.c EhciDxe.dll
DiskIo.c:
       /home/fjnh84/mine/edk2/Build/OvmfIa32/DEBUG_GCC44/IA32/MdeModulePkg/Universal/Disk/DiskIoDxe/DiskIoDxe/DEBUG/DiskIoDxe.dll
               .text, [0x17e4c240-0x17e4f440)
Dispatcher.c:
       /home/fjnh84/mine/edk2/Build/OvmfIa32/DEBUG_GCC44/IA32/MdeModulePkg/Core/Dxe/DxeMain/DEBUG/DxeCore.dll
               .text, [0x17fb1240-0x17fcb8c0)
EhciDxe.dll:
       /home/fjnh84/mine/edk2/Build/OvmfIa32/DEBUG_GCC44/IA32/MdeModulePkg/Bus/Pci/EhciDxe/EhciDxe/DEBUG/EhciDxe.dll
               .text, [0x17deb240-0x17df3920)

Patch is at https://github.com/andreiw/andreiw-wip/blob/master/gdb/0001-GDB-Initial-prototype-of-symbol-file-scope-module-sc.patch.

Works like a charm :-).
(gdb) print *'DxeCore.dll'::gST
$14 = {Hdr = {Signature = 6076298535811760713, Revision = 131102, HeaderSize = 72, CRC32 = 2051810456, Reserved = 0}, FirmwareVendor = 0x17f7f990,
  FirmwareRevision = 65536, ConsoleInHandle = 0x17c06d90, ConIn = 0x17e67424, ConsoleOutHandle = 0x17c06790, ConOut = 0x17e6752c, StandardErrorHandle = 0x17191690,
  StdErr = 0x17e675cc, RuntimeServices = 0x17f7ff10, BootServices = 0x17fc9ae8, NumberOfTableEntries = 6, ConfigurationTable = 0x17f7ed90}
(gdb) print &'DxeCore.dll'::gST
$15 = (EFI_SYSTEM_TABLE **) 0x17fca6f0
(gdb) print &'IsaBusDxe.dll'::gST
$16 = (EFI_SYSTEM_TABLE **) 0x17e21da4

Monday, May 16, 2011

GDB scripting example: reloading symbols for UEFI target

As part of helping the GSoC student with debugging, I got tired of manually groking output and loading module symbols in GDB, because it's a long and painful process. I knew GDB had scripting facilities, so I figured I'd use them. Never in my wildest dreams did I expect Python support (whoo, whoo!).

To be honest, I fully expect every serious place working with UEFI and using GDB already has something like this. But no one has been sharing...and I really don't mind. My Python is not too 1337, but I'm pretty happy with the end result.

GSoC

Here is the latest from Xen UEFI land:
  • We can boot to UEFI Shell, finally. The hurdles were -
    • Different PCI I/O range compared to Qemu, this resulted in a domain crash when an emulated I/O device tried to register for already registered ports. Boo.
    • ACPI timer block hardcoded by Xen at I/O 0xB000. Yes, modifying the "special BAR" has no effect, so UEFI TimerLib code needs to be careful in configuring the timer... if the PCI registers already look configured - use selected values. They are now set once in hvmloader.
    • 8259 PIC ExtInt routing needed to be enabled in APIC inside hvmloader, because TianoCore has no APIC support.
  • Started making OvmfPkg Xen aware. This was necessarily for at least registering the right I/O ranges. Now, the hypercall pages are populated and a GUIDed HOB containing Xen hypervisor info is published. This will be consumed by the hypervisor DXE.
I see the overall layering as being something like -
  • Hypervisor driver - consumes HOB, publishes the EFI_XEN_HYPERCALL_PROTOCOL.
  • XenTables driver - exposes Xen ACPI and SMBIOS tables, which were built into hvmloader.
  • XenBus driver - consumes Hypervisor driver, creates child nodes for virtual I/O devices, publishes EFI_XEN_BUS_PROTOCOL.
  • XenStore driver - exercises XenBus interface, allows SIMPLE_FILE_IO_PROTOCOL access to XenStore nodes.
  • Blockfront, Netfront, Fbfront drivers for block, network and video, respectively.
Latest patches as usual at https://github.com/andreiw/andreiw-wip/tree/master/xen/ovmf-support.

Friday, May 6, 2011

UEFI OVMF/Xen

For anyone interested in the GSoC project status:
- We can boot the 32-bit and the 64-bit OVMF image now inside an HVM domain instead of BIOS
- We end up crashing the HVM domain during PCI bus enumeration (qemu-dm crash?), which is what the student is now investigating...

Xen changes so far included refactoring changes inside hvmloader, adding a new bios_config structure for OVMF images, ensuring that guest physical memory under 4GB is backed with pages, as well as moving out Xen structures from below 4GB where they would collide with the firmware. The hvmloader/bios xenstore key was exposed to xend/xm (hvmbios parameter, values can be rombios, ovmf32 and ovmf64), to faciliate easier testing. Some thought still needs to be put into libxl (xend replacement) changes.
In general OVMF/Xen patches for Xen and TianoCore will be at https://github.com/andreiw/andreiw-wip/tree/master/xen/ovmf-support

Sunday, May 23, 2010

A long time ago in a galaxy far away...


This is a 32-bit UEFI firmware based on UEFI EDK. As per UEFI specification - unpaged protected mode. This is was around 3.5 years ago, before the time of OVMF, and involved filling in all the missing bits to make the Nt32 simulator real firmware - low-level code, chipset support, patched build tools to properly relocate execute-in-place PE32 binaries for SEC/PEI phases. Sure, I'd done it all before, but this time I did everything carefully and non-hacky. And this time it took me no more than 5 all-nighters, ignored lectures and "work" at my then job...but that's a stark comparison to the month/month and a half it took me to realize the same for x64 before =).

I had followed the same pattern of Tiano porting - maintaining separate SEC, PEI and DXE phases, even though in the context of a virtual machine, the PEI phase had nothing to do but load the DXE core...

The platform changes have been lost forever, while the tool patches to generate proper ROMs (fitting to my own specification as to how it should work, given lack of functioning code provided by Intel) have carried on with me into my more official EFI endeavors, but as actual code are also either gone or bit rotting on some harddrive in one of my old boxes... I've never gone past booting to the EFI shell - I think my plans were to test ELILO, but given my senior year at UIC and other concerns, I never came around to it.

P.S.: Ignore the silly SVN commit comment. You definitely don't want to use the TSC for the timer calibration, given that the TSC may fluctuate depending on CPU power savings....

Maybe I should do a port for ARMv5 Integrator/CP in QEMU...