diff --git a/dxe/Makefile b/dxe/Makefile new file mode 100644 index 0000000..d09d8f4 --- /dev/null +++ b/dxe/Makefile @@ -0,0 +1,53 @@ +KERNEL = $(shell uname -s) +CC = $(CROSS)gcc + +TARGETS += fvloader.ffs +TARGETS += hello.ffs + +all: $(TARGETS) + +clean: FORCE + $(RM) *.efi *.exe *.rom *.o .*.d $(TARGETS) + + +FORCE: + +%.efi: %.exe + CROSS=$(CROSS) \ + ./efi-wrap $< > $@ + +%.ffs: %.efi + ../bin/create-ffs \ + -o $@ \ + -t DRIVER \ + -n "$(basename $@)" \ + $< + +BITS=64 +EFI_ARCH=x86_64 +#BITS=32 +#EFI_ARCH=x86 + +%.exe: %.o + $(CROSS)ld \ + -T pei-x86-$(BITS).lds \ + -o $@ \ + $^ + +CFLAGS += \ + -std=c99 \ + -D__efi__ \ + -nostdinc \ + -fshort-wchar \ + -mno-red-zone \ + -fno-stack-protector \ + -m$(BITS) \ + -fpic \ + -O3 \ + -W \ + -Wall \ + -I . \ + -MMD \ + -MF .$(notdir $@).d \ + +-include .*.d diff --git a/dxe/README.md b/dxe/README.md new file mode 100644 index 0000000..a8c7f23 --- /dev/null +++ b/dxe/README.md @@ -0,0 +1,84 @@ +Overview +=== + +These are sample EFI Option ROMs that can be installed in Apple's +Thunderbolt Gigabit Ethernet adapter. Anything that does printouts +to the console device will only be visible if you have set the +`bootargs` NVRAM parameter to verbose mode: + + sudo nvram bootargs=-v + +Once you have build the `hello.rom` file, install it with the `b57tool`. +Unlike Broadcom's `B57UDAIG.EXE`, this does not require rebooting to DOS +and works with a hot-plugged Thunderbolt device: + + sudo ../tools/b57tool --pxe hello.rom + +This tool does require that you have installed the `DirectHW.kext` from +the CoreBoot project. + + +Developing ROMs +=== + +Calling conventions +--- +The EFI environment uses the Microsoft ABI, so gcc must be told which +functions are called from or call into the EFI system. This is done +with the `EFIAPI` macro, which annotates the functions with the gcc +x86 extension [`__attribute__((ms_abi))`](https://gcc.gnu.org/onlinedocs/gcc/x86-Function-Attributes.html#x86-Function-Attributes) + +The entry point into the Option ROM must be named `efi_main()` and +should have the prototype: + + EFI_STATUS + EFIAPI + efi_main( + EFI_HANDLE image, + EFI_SYSTEM_TABLE * st + ); + +Any callbacks that are registered, such as for the `ExitBootServices` event, +must also be flagged with `EFIAPI`. + + +Console I/O +--- +It is possible to print to the screen while EFI is running. The +`EFI_SYSTEM_TABLE` struct has a `SIMPLE_TEXT_OUTPUT_INTERFACE` pointer `ConOut` +to a struct with an `OutputString()` function pointer. This function takes +UCS-2 wide characters: + + st->ConOut->OutputString(st->ConOut, L"Hello, world!\n"); + +The screen and the console will likely not be setup when `efi_main()` is +called, so it is typical to register a callback for `ExitBootServices` +to do any output. + +As noted above, it is necessary to boot the system in "verbose" mode to +see any output from EFI. Set the `bootargs` NVRAM variable to configure this: + + sudo nvram bootargs=-v + +Reading keystrokes for a shell or similar can be done with the `ConIn` +struct. I have not figured out how to use it, but I have figured out how +to hook it to read key strokes. See `roms/keylogger.c` for an example. + + +Memory allocation +--- +There are lots of pools of memory allocation during EFI, some of which are +cleared when the OS starts, some of which stay resident, etc. In general +you can request memory with: + + void * buf; + + if (gST->BootServices->AllocatePool( + EfiBootServicesData, + len, + &buf + ) != 0) { + // handle an error... + } + + diff --git a/dxe/efi-wrap b/dxe/efi-wrap new file mode 100755 index 0000000..172fff0 --- /dev/null +++ b/dxe/efi-wrap @@ -0,0 +1,115 @@ +#!/usr/bin/perl +# Convert a gcc/mingw compiled application to a valid EFI executable +# by reuseing a valid header. The linked doesn't generate the right +# fields for some reason. +# +# This is a total hack and should not be used by anything real. +# We really should figure out how to make this work the right way. +# +# 2015-04-21 +use warnings; +use strict; + +my $code_size_offset = 0x9C; +my $data_size_offset = 0xA0; +my $entry_offset = 0xA8; +my $code_offset = 0xAC; +my $CROSS = $ENV{CROSS} || ""; + +my $hdr = undump(); +my $file = shift; + +my $input = `${CROSS}objcopy -O binary "$file" "/tmp/$$.bin"; cat "/tmp/$$.bin"`; +my $header = `${CROSS}readelf -h "$file"`; +my $len = length $input; + +my ($entry) = $header =~ /Entry point.*?(0x[0-9a-f]*)$/msg + or die "$file: Unable to parse entry point\n"; +$entry = hex($entry) - 0x1e0; +my $code_start = 0; +my $code_size = length($input); +my $data_size = 0; + + +sub undump +{ + my $bin; + + for (@_) + { + my $bytes = substr($_, 9, 16*3); + $bin .= join '', map { chr(hex $_) } split / /, $bytes; + } + + return $bin; +} + +sub offset +{ + my $offset = shift; + return unpack("L", substr($input, $offset, 4)); +} + + +#my $entry = offset($entry_offset); +#my $code_start = offset($code_offset); +#my $code_size = offset($code_size_offset); +#my $data_size = offset($data_size_offset); + +my $entry_hex = sprintf "%04x", $entry; +warn sprintf "Read %d bytes; entry %04x, code %04x @ %04x, data %04x\n", + $len, + $entry, + $code_start, + $code_size, + $data_size, + ; + +# fixup the entry point based on the difference in the code start values +$entry = ($entry - $code_start) + length($hdr); + +warn sprintf "New entry: %04x\n", $entry; + +my $img = $hdr . substr($input, $code_start); +substr($img, $entry_offset, 4) = pack("L", $entry); + +print $img; + + +__END__ +0000000: 4d 5a 00 00 00 00 00 00 00 00 00 00 00 00 00 00 MZ.............. +0000010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ +0000020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ +0000030: 00 00 00 00 00 00 00 00 00 00 00 00 80 00 00 00 ................ +0000040: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ +0000050: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ +0000060: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ +0000070: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ +0000080: 50 45 00 00 64 86 04 00 00 00 00 00 a0 e5 00 00 PE..d........... +0000090: 00 00 00 00 f0 00 0e 03 0b 02 02 38 00 00 00 00 ...........8.... +00000a0: 00 00 00 00 00 00 00 00 7f 8e 00 00 40 02 00 00 ............@... +00000b0: 00 00 00 00 00 00 00 00 20 00 00 00 20 00 00 00 ........ ... ... +00000c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ +00000d0: a0 e5 00 00 40 02 00 00 4f aa 01 00 0b 00 00 00 ....@...O....... +00000e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ +00000f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ +0000100: 00 00 00 00 10 00 00 00 00 00 00 00 00 00 00 00 ................ +0000110: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ +0000120: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ +0000130: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ....H...`...1... +0000140: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ +0000150: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ +0000160: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ +0000170: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ +0000180: 00 00 00 00 00 00 00 00 2e 74 65 78 74 00 00 00 .........text... +0000190: a0 db 00 00 40 02 00 00 a0 db 00 00 40 02 00 00 ....@.......@... +00001a0: 00 00 00 00 00 00 00 00 00 00 00 00 20 00 00 60 ............ ..` +00001b0: 2e 64 61 74 61 00 00 00 20 07 00 00 e0 dd 00 00 .data... ....... +00001c0: e0 02 00 00 e0 dd 00 00 00 00 00 00 00 00 00 00 ................ +00001d0: 00 00 00 00 60 00 00 e0 2e 72 65 6c 6f 63 00 00 ....`....reloc.. +00001e0: 48 00 00 00 00 e5 00 00 60 00 00 00 00 e5 00 00 H.......`....... +00001f0: 00 00 00 00 00 00 00 00 00 00 00 00 60 00 00 62 ............`..b +0000200: 2e 64 65 62 75 67 00 00 31 00 00 00 60 e5 00 00 .debug..1...`... +0000210: 40 00 00 00 60 e5 00 00 00 00 00 00 00 00 00 00 @...`........... +0000220: 00 00 00 00 60 00 00 62 00 00 00 00 00 00 00 00 ....`..b........ +0000230: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ diff --git a/dxe/efi.h b/dxe/efi.h new file mode 100644 index 0000000..92fe231 --- /dev/null +++ b/dxe/efi.h @@ -0,0 +1,110 @@ +#ifndef __efi_h__ +#define __efi_h__ + +/* Just enough of the EFI API to write some code */ + +#define EFIAPI __attribute__((ms_abi)) +#define NULL 0 +typedef int EFI_STATUS; +typedef unsigned char uint8_t; +typedef unsigned short uint16_t; +typedef unsigned int uint32_t; +typedef unsigned long uint64_t; +typedef unsigned short wchar_t; + +typedef void * EFI_HANDLE; + +typedef struct { + uint64_t Signature; + uint32_t Revision; + uint32_t HeaderSize; + uint32_t CRC32; + uint32_t Reserved; +} EFI_TABLE_HEADER; + +typedef struct { + uint32_t Data1; + uint16_t Data2; + uint16_t Data3; + uint8_t Data4[8]; +} EFI_GUID; + + +typedef struct { + EFI_TABLE_HEADER Hdr; + void* AddMemorySpace; + void* AllocateMemorySpace; + void* FreeMemorySpace; + void* RemoveMemorySpace; + void* GetMemorySpaceDescriptor; + void* SetMemorySpaceAttributes; + void* GetMemorySpaceMap; + void* AddIoSpace; + void* AllocateIoSpace; + void* FreeIoSpace; + void* RemoveIoSpace; + void* GetIoSpaceDescriptor; + void* GetIoSpaceMap; + void* Dispatch; + void* Schedule; + void* Trust; + EFI_STATUS EFIAPI (*ProcessFirmwareVolume)( + void * buffer, + unsigned len, + EFI_HANDLE * handle_out + ); +} EFI_DXE_SERVICES; + +typedef struct { + EFI_GUID VendorGuid; + void *VendorTable; +} EFI_CONFIGURATION_TABLE; + +typedef struct { + EFI_TABLE_HEADER Hdr; + + wchar_t *FirmwareVendor; + uint32_t FirmwareRevision; + + void* ConsoleInHandle; + void* *ConIn; + + void* ConsoleOutHandle; + void* *ConOut; + + void* StandardErrorHandle; + void* *StdErr; + + void* *RuntimeServices; + void* *BootServices; + + unsigned NumberOfTableEntries; + EFI_CONFIGURATION_TABLE *ConfigurationTable; + +} EFI_SYSTEM_TABLE; + + +static inline void * +efi_find_table( + EFI_SYSTEM_TABLE * st, + uint32_t search_guid +) +{ + const EFI_CONFIGURATION_TABLE * ct = st->ConfigurationTable; + + serial_string("num tables="); + serial_hex(st->NumberOfTableEntries, 4); + + for(unsigned i = 0 ; i < st->NumberOfTableEntries ; i++) + { + const EFI_GUID * guid = &ct[i].VendorGuid; + serial_hex(*(uint64_t*)guid, 16); + if (guid->Data1 == search_guid) + return ct[i].VendorTable; + + } + + return NULL; +} + +#endif diff --git a/dxe/fvloader.c b/dxe/fvloader.c new file mode 100644 index 0000000..57175e2 --- /dev/null +++ b/dxe/fvloader.c @@ -0,0 +1,53 @@ +/** \file + * Tell DxeCore about an alternate firmware volume in the ROM. + * + * This allows LinuxBoot to locate the Linux kernel and initrd + * outside of the normal DXE volume, which is quite small on some + * systems. + */ +// #define VOLUME_ADDRESS 0xFF840000 // Winterfell +// #define VOLUME_LENGTH 0x20000 + +// Heron +#define VOLUME_ADDRESS 0xFF410000 +#define VOLUME_LENGTH 0x00800000 + +#include "serial.h" +#include "efi.h" + + +EFI_STATUS +EFIAPI +efi_main( + EFI_HANDLE image, + EFI_SYSTEM_TABLE * const st +) +{ + (void) image; + + //gST = st; + //gBS = gST->BootServices; + //gRT = gST->RuntimeServices; + + const EFI_DXE_SERVICES * dxe_services = efi_find_table(st, 0x5ad34ba); + + if (!dxe_services) + { + serial_string("FvLoader: No DXE system table found...\r\n"); + return 0x80000001; + } + + serial_string("FvLoader: adding firmware volume 0x"); + serial_hex(VOLUME_ADDRESS, 8); + + EFI_HANDLE handle; + int rc = dxe_services->ProcessFirmwareVolume( + (void*) VOLUME_ADDRESS, + VOLUME_LENGTH, + &handle + ); + + serial_string("FvLoader: rc="); serial_hex(rc, 8); + + return rc; +} diff --git a/dxe/hello.c b/dxe/hello.c new file mode 100644 index 0000000..fb43638 --- /dev/null +++ b/dxe/hello.c @@ -0,0 +1,22 @@ +/** \file + */ +#include "serial.h" +#include "efi.h" + + +EFI_STATUS +EFIAPI +efi_main( + EFI_HANDLE image, + EFI_SYSTEM_TABLE * const st +) +{ + (void) image; + (void) st; + + serial_string("+---------------+\r\n"); + serial_string("| Hello, world! |\r\n"); + serial_string("+---------------+\r\n"); + + return 0; +} diff --git a/dxe/pei-x86-32.lds b/dxe/pei-x86-32.lds new file mode 100644 index 0000000..427ad01 --- /dev/null +++ b/dxe/pei-x86-32.lds @@ -0,0 +1,30 @@ +OUTPUT_FORMAT("elf32-i386", "elf32-i386", "elf32-i386") +/*OUTPUT_FORMAT("pei-x86-64", "pei-x86-64", "pei-x86-64") */ +OUTPUT_ARCH(i386) +ENTRY(efi_main) +SECTIONS +{ + /* 0x1e0 is the size of the MZ header that will be added by gcc */ + .text 0x1e0 : + { + *(.text) + } + + .data : + { + *(.rodata*) + *(.data*) + /* the EFI loader doesn't seem to like a .bss section, so we stick + it all into .data: */ + *(.bss) + } + + /DISCARD/ : + { + *(.xdata*) + *(.idata*) + *(.pdata*) + *(.comment) + *(.eh_fram*) + } +} diff --git a/dxe/pei-x86-64.lds b/dxe/pei-x86-64.lds new file mode 100644 index 0000000..bc16c78 --- /dev/null +++ b/dxe/pei-x86-64.lds @@ -0,0 +1,30 @@ +OUTPUT_FORMAT("elf64-x86-64", "elf64-x86-64", "elf64-x86-64") +/*OUTPUT_FORMAT("pei-x86-64", "pei-x86-64", "pei-x86-64") */ +OUTPUT_ARCH(i386:x86-64) +ENTRY(efi_main) +SECTIONS +{ + /* 0x1e0 is the size of the MZ header that will be added by gcc */ + .text 0x1e0 : + { + *(.text) + } + + .data : + { + *(.rodata*) + *(.data*) + /* the EFI loader doesn't seem to like a .bss section, so we stick + it all into .data: */ + *(.bss) + } + + /DISCARD/ : + { + *(.xdata*) + *(.idata*) + *(.pdata*) + *(.comment) + *(.eh_fram*) + } +} diff --git a/dxe/serial.h b/dxe/serial.h new file mode 100644 index 0000000..da37328 --- /dev/null +++ b/dxe/serial.h @@ -0,0 +1,66 @@ +#ifndef __serial_h__ +#define __serial_h__ + +static __inline void +outb (unsigned char __value, unsigned short int __port) +{ + __asm__ __volatile__ ("outb %b0,%w1": :"a" (__value), "Nd" (__port)); +} + +static __inline unsigned char +inb (unsigned short int __port) +{ + unsigned char _v; + + __asm__ __volatile__ ("inb %w1,%0":"=a" (_v):"Nd" (__port)); + return _v; +} + +#define PORT 0x3f8 /* COM1 */ + +#define DLAB 0x80 + +#define TXR 0 /* Transmit register (WRITE) */ +#define RXR 0 /* Receive register (READ) */ +#define IER 1 /* Interrupt Enable */ +#define IIR 2 /* Interrupt ID */ +#define FCR 2 /* FIFO control */ +#define LCR 3 /* Line control */ +#define MCR 4 /* Modem control */ +#define LSR 5 /* Line Status */ +#define MSR 6 /* Modem Status */ +#define DLL 0 /* Divisor Latch Low */ +#define DLH 1 /* Divisor latch High */ + +static int is_transmit_empty() +{ + return inb(PORT + 5) & 0x20; +} + +static void serial_char(char a) { + outb(a, PORT); + while (is_transmit_empty() == 0); +} + +static void serial_string(const char * s) +{ + while(*s) + serial_char(*s++); +} + +static void serial_hex(unsigned long x, unsigned digits) +{ + while(digits-- > 0) + { + unsigned d = (x >> (digits * 4)) & 0xF; + if (d >= 0xA) + serial_char(d + 'A' - 0xA); + else + serial_char(d + '0'); + } + serial_char('\r'); + serial_char('\n'); +} + + +#endif