Skip to content

Commit 3d73eb1

Browse files
committed
Add EFI support for amd64 and arm64 platforms.
Some decisions points: - no external libc dependency, builtin functions replaced by Go equivalent (runtime_minimal_libc.go) - assembly efi_main entrypoint as assembly stub is required for sbat and few symbols anyway - a default sbat section (required for Linux shim secure boot) - dedicated rand reader in crypto/rand for EFI as it doesn't fit well with the generic machine interface - the timer calibration is deliberately simple but hopefully good enough for EFI purpose - no heap grow support, just get the biggest memory region available
1 parent db9f118 commit 3d73eb1

32 files changed

+1940
-69
lines changed

GNUmakefile

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -814,6 +814,18 @@ endif
814814
@$(MD5SUM) test.hex
815815
$(TINYGO) build -size short -o test.hex -target=waveshare-rp2040-tiny examples/echo
816816
@$(MD5SUM) test.hex
817+
$(TINYGO) build -size short -o test.exe -target=uefi-amd64 examples/echo2
818+
@$(MD5SUM) test.exe
819+
$(TINYGO) build -size short -o test.exe -target=uefi-amd64 examples/empty
820+
@$(MD5SUM) test.exe
821+
$(TINYGO) build -size short -o test.exe -target=uefi-amd64 examples/time-offset
822+
@$(MD5SUM) test.exe
823+
$(TINYGO) build -size short -o test.exe -target=uefi-arm64 examples/echo2
824+
@$(MD5SUM) test.exe
825+
$(TINYGO) build -size short -o test.exe -target=uefi-arm64 examples/empty
826+
@$(MD5SUM) test.exe
827+
$(TINYGO) build -size short -o test.exe -target=uefi-arm64 examples/time-offset
828+
@$(MD5SUM) test.exe
817829
# test pwm
818830
$(TINYGO) build -size short -o test.hex -target=itsybitsy-m0 examples/pwm
819831
@$(MD5SUM) test.hex

builder/build.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import (
1919
"os/exec"
2020
"path/filepath"
2121
"runtime"
22+
"slices"
2223
"sort"
2324
"strconv"
2425
"strings"
@@ -813,6 +814,11 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
813814
ldflags = append(ldflags,
814815
"-Xlink=/opt:lldlto="+strconv.Itoa(speedLevel),
815816
"--thinlto-cache-dir="+filepath.Join(cacheDir, "thinlto"))
817+
} else if slices.Contains(config.Target.BuildTags, "uefi") {
818+
// PE/COFF targets (e.g. UEFI) that use GOOS != "windows"
819+
// but still link with ld.lld via -m i386pep/arm64pe.
820+
// The PE/COFF linker --lto-O option is not supported on
821+
// all versions of ld.lld, so ignore it.
816822
} else if config.GOOS() == "darwin" {
817823
// Options for the ld64-compatible lld linker.
818824
ldflags = append(ldflags,

builder/builder_test.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"os"
66
"path/filepath"
77
"runtime"
8+
"strings"
89
"testing"
910

1011
"github.com/tinygo-org/tinygo/compileopts"
@@ -35,6 +36,8 @@ func TestClangAttributes(t *testing.T) {
3536
"nintendoswitch",
3637
"riscv-qemu",
3738
"tkey",
39+
"uefi-amd64",
40+
"uefi-arm64",
3841
"wasip1",
3942
"wasip2",
4043
"wasm",
@@ -128,7 +131,7 @@ func testClangAttributes(t *testing.T, options *compileopts.Options) {
128131
defer mod.Dispose()
129132

130133
// Check whether the LLVM target matches.
131-
if mod.Target() != config.Triple() {
134+
if mod.Target() != config.Triple() || !strings.HasPrefix(mod.Target(), config.Triple()) {
132135
t.Errorf("target has LLVM triple %#v but Clang makes it LLVM triple %#v", config.Triple(), mod.Target())
133136
}
134137

emulators/qemu-uefi-aarch64.sh

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
#!/bin/sh
2+
3+
set -e
4+
5+
binary=$1
6+
dir=$(dirname $binary)
7+
8+
mkdir -p ${dir}/qemu-disk/efi/boot
9+
mv $binary ${dir}/qemu-disk/efi/boot/bootaa64.efi
10+
11+
ovmf_path=${OVMF_CODE:-/usr/share/AAVMF/AAVMF_CODE.no-secboot.fd}
12+
13+
exec qemu-system-aarch64 -M virt -bios $ovmf_path -device virtio-rng-pci \
14+
-cpu max,pauth-impdef=on -drive format=raw,file=fat:rw:${dir}/qemu-disk \
15+
-net none -nographic -no-reboot

emulators/qemu-uefi-x86_64.sh

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#!/bin/sh
2+
3+
set -e
4+
5+
binary=$1
6+
dir=$(dirname $binary)
7+
8+
mkdir -p ${dir}/qemu-disk/efi/boot
9+
mv $binary ${dir}/qemu-disk/efi/boot/bootx64.efi
10+
11+
ovmf_path=${OVMF_CODE:-/usr/share/qemu/OVMF.fd}
12+
13+
exec qemu-system-x86_64 -bios $ovmf_path -cpu qemu64,+rdrand -device virtio-rng-pci \
14+
-drive format=raw,file=fat:rw:${dir}/qemu-disk -net none -nographic -no-reboot

src/crypto/rand/rand_uefi.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
//go:build uefi
2+
3+
package rand
4+
5+
import (
6+
"errors"
7+
"machine/uefi"
8+
)
9+
10+
func init() {
11+
Reader = &reader{}
12+
}
13+
14+
type reader struct {
15+
}
16+
17+
func (r *reader) Read(b []byte) (n int, err error) {
18+
if !uefi.HasRNGSupport() {
19+
return 0, errors.New("no hardware rng available")
20+
} else if len(b) == 0 {
21+
return 0, nil
22+
}
23+
24+
var randomByte uint64
25+
for i := range b {
26+
if i%8 == 0 {
27+
var ok bool
28+
randomByte, ok = uefi.ReadRandom()
29+
if !ok {
30+
return n, errors.New("no random seed available")
31+
}
32+
} else {
33+
randomByte >>= 8
34+
}
35+
b[i] = byte(randomByte)
36+
}
37+
38+
return len(b), nil
39+
}

src/internal/task/task_stack_amd64.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
//go:build scheduler.tasks && amd64 && !windows
1+
//go:build scheduler.tasks && amd64 && !windows && !uefi
22

33
package task
44

src/internal/task/task_stack_amd64_windows.go renamed to src/internal/task/task_stack_amd64_winabi.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
//go:build scheduler.tasks && amd64 && windows
1+
//go:build scheduler.tasks && amd64 && (windows || uefi)
22

33
package task
44

src/machine/uefi/api.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
//go:build uefi
2+
3+
package uefi
4+
5+
// callAsm is the single assembly stub for all UEFI calls.
6+
// It takes a function pointer, a pointer to an argument array, and the count.
7+
//
8+
//export uefiCall
9+
func callAsm(fn uintptr, args *uintptr, nargs uintptr) Status
10+
11+
// Call invokes a UEFI function with the given arguments via the MS x64 ABI.
12+
func Call(fn uintptr, args ...uintptr) Status {
13+
if len(args) == 0 {
14+
return callAsm(fn, nil, 0)
15+
}
16+
return callAsm(fn, &args[0], uintptr(len(args)))
17+
}

src/machine/uefi/api_amd64.S

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
// UEFI Call Stub for x86_64
2+
//
3+
// Generic stub for calling any UEFI function via the Microsoft x64 ABI.
4+
//
5+
// Go signature: func callAsm(fn uintptr, args *uintptr, nargs uintptr) Status
6+
// MS x64 entry:
7+
// RCX = fn (function pointer to call)
8+
// RDX = args (pointer to uintptr array of arguments)
9+
// R8 = nargs (number of arguments, 0-N)
10+
//
11+
// MS x64 ABI register mapping for the callee:
12+
// Args 1-4: RCX, RDX, R8, R9
13+
// Args 5+: stack (after 32-byte shadow space)
14+
// Return: RAX
15+
// Stack: 16-byte aligned before CALL
16+
//
17+
18+
.section .text.uefiCall,"ax"
19+
.global uefiCall
20+
uefiCall:
21+
pushq %rbp
22+
movq %rsp, %rbp
23+
pushq %rsi
24+
pushq %rdi
25+
26+
movq %rcx, %r10 // Save fn
27+
movq %rdx, %rsi // Save args pointer
28+
movq %r8, %rdi // Save nargs
29+
30+
// Calculate stack space: align16(32 + max(0, nargs-4) * 8)
31+
// After 3 pushes, RSP is 16-byte aligned. Subtracting an aligned
32+
// value keeps it aligned for the CALL instruction.
33+
xorq %rax, %rax
34+
cmpq $4, %rdi
35+
jle 1f
36+
movq %rdi, %rax
37+
subq $4, %rax
38+
1:
39+
shlq $3, %rax // nStackArgs * 8
40+
addq $47, %rax // + 32 (shadow) + 15 (round-up)
41+
andq $-16, %rax // align to 16
42+
subq %rax, %rsp
43+
44+
// Copy stack args: args[4..nargs-1] -> RSP+32+j*8
45+
movq %rdi, %rcx
46+
subq $4, %rcx
47+
jle 2f
48+
xorq %rax, %rax
49+
3: movq 32(%rsi,%rax,8), %r11
50+
movq %r11, 32(%rsp,%rax,8)
51+
incq %rax
52+
cmpq %rcx, %rax
53+
jl 3b
54+
2:
55+
// Load register args
56+
xorq %rcx, %rcx
57+
xorq %rdx, %rdx
58+
xorq %r8, %r8
59+
xorq %r9, %r9
60+
61+
testq %rdi, %rdi
62+
jz 4f
63+
movq 0(%rsi), %rcx // args[0] -> RCX
64+
cmpq $2, %rdi
65+
jl 4f
66+
movq 8(%rsi), %rdx // args[1] -> RDX
67+
cmpq $3, %rdi
68+
jl 4f
69+
movq 16(%rsi), %r8 // args[2] -> R8
70+
cmpq $4, %rdi
71+
jl 4f
72+
movq 24(%rsi), %r9 // args[3] -> R9
73+
4:
74+
callq *%r10
75+
76+
leaq -16(%rbp), %rsp
77+
popq %rdi
78+
popq %rsi
79+
popq %rbp
80+
retq

0 commit comments

Comments
 (0)