Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions GNUmakefile
Original file line number Diff line number Diff line change
Expand Up @@ -814,6 +814,18 @@ endif
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=waveshare-rp2040-tiny examples/echo
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.exe -target=uefi-amd64 examples/echo2
@$(MD5SUM) test.exe
$(TINYGO) build -size short -o test.exe -target=uefi-amd64 examples/empty
@$(MD5SUM) test.exe
$(TINYGO) build -size short -o test.exe -target=uefi-amd64 examples/time-offset
@$(MD5SUM) test.exe
$(TINYGO) build -size short -o test.exe -target=uefi-arm64 examples/echo2
@$(MD5SUM) test.exe
$(TINYGO) build -size short -o test.exe -target=uefi-arm64 examples/empty
@$(MD5SUM) test.exe
$(TINYGO) build -size short -o test.exe -target=uefi-arm64 examples/time-offset
@$(MD5SUM) test.exe
# test pwm
$(TINYGO) build -size short -o test.hex -target=itsybitsy-m0 examples/pwm
@$(MD5SUM) test.hex
Expand Down
3 changes: 2 additions & 1 deletion builder/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"os/exec"
"path/filepath"
"runtime"
"slices"
"sort"
"strconv"
"strings"
Expand Down Expand Up @@ -808,7 +809,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
}
ldflags = append(ldflags, "-mllvm", "-mcpu="+config.CPU())
ldflags = append(ldflags, "-mllvm", "-mattr="+config.Features()) // needed for MIPS softfloat
if config.GOOS() == "windows" {
if config.GOOS() == "windows" || slices.Contains(config.Target.BuildTags, "uefi") {
// Options for the MinGW wrapper for the lld COFF linker.
ldflags = append(ldflags,
"-Xlink=/opt:lldlto="+strconv.Itoa(speedLevel),
Expand Down
5 changes: 4 additions & 1 deletion builder/builder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"os"
"path/filepath"
"runtime"
"strings"
"testing"

"github.com/tinygo-org/tinygo/compileopts"
Expand Down Expand Up @@ -35,6 +36,8 @@ func TestClangAttributes(t *testing.T) {
"nintendoswitch",
"riscv-qemu",
"tkey",
"uefi-amd64",
"uefi-arm64",
"wasip1",
"wasip2",
"wasm",
Expand Down Expand Up @@ -128,7 +131,7 @@ func testClangAttributes(t *testing.T, options *compileopts.Options) {
defer mod.Dispose()

// Check whether the LLVM target matches.
if mod.Target() != config.Triple() {
if mod.Target() != config.Triple() && !strings.HasPrefix(mod.Target(), config.Triple()) {
t.Errorf("target has LLVM triple %#v but Clang makes it LLVM triple %#v", config.Triple(), mod.Target())
}

Expand Down
15 changes: 15 additions & 0 deletions emulators/qemu-uefi-aarch64.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#!/bin/sh

set -e

binary=$1
dir=$(dirname $binary)

mkdir -p ${dir}/qemu-disk/efi/boot
mv $binary ${dir}/qemu-disk/efi/boot/bootaa64.efi

ovmf_path=${OVMF_CODE:-/usr/share/AAVMF/AAVMF_CODE.no-secboot.fd}

exec qemu-system-aarch64 -M virt -bios $ovmf_path -device virtio-rng-pci \
-cpu max,pauth-impdef=on -drive format=raw,file=fat:rw:${dir}/qemu-disk \
-net none -nographic -no-reboot
14 changes: 14 additions & 0 deletions emulators/qemu-uefi-x86_64.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#!/bin/sh

set -e

binary=$1
dir=$(dirname $binary)

mkdir -p ${dir}/qemu-disk/efi/boot
mv $binary ${dir}/qemu-disk/efi/boot/bootx64.efi

ovmf_path=${OVMF_CODE:-/usr/share/qemu/OVMF.fd}

exec qemu-system-x86_64 -bios $ovmf_path -cpu qemu64,+rdrand -device virtio-rng-pci \
-drive format=raw,file=fat:rw:${dir}/qemu-disk -net none -nographic -no-reboot
39 changes: 39 additions & 0 deletions src/crypto/rand/rand_uefi.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
//go:build uefi

package rand

import (
"errors"
"machine/uefi"
)

func init() {
Reader = &reader{}
}

type reader struct {
}

func (r *reader) Read(b []byte) (n int, err error) {
if !uefi.HasRNGSupport() {
return 0, errors.New("no hardware rng available")
} else if len(b) == 0 {
return 0, nil
}

var randomByte uint64
for i := range b {
if i%8 == 0 {
var ok bool
randomByte, ok = uefi.ReadRandom()
if !ok {
return n, errors.New("no random seed available")
}
} else {
randomByte >>= 8
}
b[i] = byte(randomByte)
}

return len(b), nil
}
2 changes: 1 addition & 1 deletion src/internal/task/task_stack_amd64.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//go:build scheduler.tasks && amd64 && !windows
//go:build scheduler.tasks && amd64 && !windows && !uefi

package task

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//go:build scheduler.tasks && amd64 && windows
//go:build scheduler.tasks && amd64 && (windows || uefi)

package task

Expand Down
17 changes: 17 additions & 0 deletions src/machine/uefi/api.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
//go:build uefi

package uefi

// callAsm is the single assembly stub for all UEFI calls.
// It takes a function pointer, a pointer to an argument array, and the count.
//
//export uefiCall
func callAsm(fn uintptr, args *uintptr, nargs uintptr) Status

// Call invokes a UEFI function with the given arguments via the MS x64 ABI.
func Call(fn uintptr, args ...uintptr) Status {
if len(args) == 0 {
return callAsm(fn, nil, 0)
}
return callAsm(fn, &args[0], uintptr(len(args)))
}
80 changes: 80 additions & 0 deletions src/machine/uefi/api_amd64.S
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// UEFI Call Stub for x86_64
//
// Generic stub for calling any UEFI function via the Microsoft x64 ABI.
//
// Go signature: func callAsm(fn uintptr, args *uintptr, nargs uintptr) Status
// MS x64 entry:
// RCX = fn (function pointer to call)
// RDX = args (pointer to uintptr array of arguments)
// R8 = nargs (number of arguments, 0-N)
//
// MS x64 ABI register mapping for the callee:
// Args 1-4: RCX, RDX, R8, R9
// Args 5+: stack (after 32-byte shadow space)
// Return: RAX
// Stack: 16-byte aligned before CALL
//

.section .text.uefiCall,"ax"
.global uefiCall
uefiCall:
pushq %rbp
movq %rsp, %rbp
pushq %rsi
pushq %rdi

movq %rcx, %r10 // Save fn
movq %rdx, %rsi // Save args pointer
movq %r8, %rdi // Save nargs

// Calculate stack space: align16(32 + max(0, nargs-4) * 8)
// After 3 pushes, RSP is 16-byte aligned. Subtracting an aligned
// value keeps it aligned for the CALL instruction.
xorq %rax, %rax
cmpq $4, %rdi
jle 1f
movq %rdi, %rax
subq $4, %rax
1:
shlq $3, %rax // nStackArgs * 8
addq $47, %rax // + 32 (shadow) + 15 (round-up)
andq $-16, %rax // align to 16
subq %rax, %rsp

// Copy stack args: args[4..nargs-1] -> RSP+32+j*8
movq %rdi, %rcx
subq $4, %rcx
jle 2f
xorq %rax, %rax
3: movq 32(%rsi,%rax,8), %r11
movq %r11, 32(%rsp,%rax,8)
incq %rax
cmpq %rcx, %rax
jl 3b
2:
// Load register args
xorq %rcx, %rcx
xorq %rdx, %rdx
xorq %r8, %r8
xorq %r9, %r9

testq %rdi, %rdi
jz 4f
movq 0(%rsi), %rcx // args[0] -> RCX
cmpq $2, %rdi
jl 4f
movq 8(%rsi), %rdx // args[1] -> RDX
cmpq $3, %rdi
jl 4f
movq 16(%rsi), %r8 // args[2] -> R8
cmpq $4, %rdi
jl 4f
movq 24(%rsi), %r9 // args[3] -> R9
4:
callq *%r10

leaq -16(%rbp), %rsp
popq %rdi
popq %rsi
popq %rbp
retq
98 changes: 98 additions & 0 deletions src/machine/uefi/api_arm64.S
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
// UEFI Call Stub for ARM64
//
// Generic stub for calling any UEFI function via the AAPCS64 calling convention.
//
// Go signature: func callAsm(fn uintptr, args *uintptr, nargs uintptr) Status
// AAPCS64 entry:
// x0 = fn (function pointer to call)
// x1 = args (pointer to uintptr array of arguments)
// x2 = nargs (number of arguments, 0-N)
//
// AAPCS64 register mapping for the callee:
// Args 1-8: x0-x7
// Args 9+: stack (no shadow space)
// Return: x0
// Stack: 16-byte aligned at all times

.section .text.uefiCall,"ax"
.global uefiCall
uefiCall:
// Save callee-saved registers and link register.
stp x29, x30, [sp, #-64]!
mov x29, sp
stp x19, x20, [sp, #16]
stp x21, x22, [sp, #32]

// Move parameters to callee-saved registers so they survive
// the stack setup and register loading below.
mov x19, x0 // x19 = fn
mov x20, x1 // x20 = args pointer
mov x21, x2 // x21 = nargs

// Calculate and reserve stack space for args 9+.
// nStackArgs = max(0, nargs - 8)
// Stack space = align16(nStackArgs * 8)
subs x22, x21, #8
b.le .Lno_stack_args

// Round up to 16-byte alignment: ((nStackArgs * 8) + 15) & ~15
lsl x3, x22, #3 // nStackArgs * 8
add x3, x3, #15
and x3, x3, #~0xF
sub sp, sp, x3

// Copy stack args: args[8..nargs-1] -> sp[0..]
add x6, x20, #64 // x6 = &args[8]
mov x4, #0 // index = 0
.Lcopy_stack:
ldr x5, [x6, x4, lsl #3]
str x5, [sp, x4, lsl #3]
add x4, x4, #1
cmp x4, x22
b.lt .Lcopy_stack

.Lno_stack_args:
// Load register arguments x0-x7 from the args array.
// Zero all registers first, then load as many as nargs specifies.
mov x0, #0
mov x1, #0
mov x2, #0
mov x3, #0
mov x4, #0
mov x5, #0
mov x6, #0
mov x7, #0

cbz x21, .Lcall // nargs == 0, skip loading
ldr x0, [x20, #0] // args[0]
cmp x21, #2
b.lt .Lcall
ldr x1, [x20, #8] // args[1]
cmp x21, #3
b.lt .Lcall
ldr x2, [x20, #16] // args[2]
cmp x21, #4
b.lt .Lcall
ldr x3, [x20, #24] // args[3]
cmp x21, #5
b.lt .Lcall
ldr x4, [x20, #32] // args[4]
cmp x21, #6
b.lt .Lcall
ldr x5, [x20, #40] // args[5]
cmp x21, #7
b.lt .Lcall
ldr x6, [x20, #48] // args[6]
cmp x21, #8
b.lt .Lcall
ldr x7, [x20, #56] // args[7]

.Lcall:
blr x19 // Call UEFI function, result in x0

// Restore stack and callee-saved registers.
mov sp, x29
ldp x19, x20, [sp, #16]
ldp x21, x22, [sp, #32]
ldp x29, x30, [sp], #64
ret
21 changes: 21 additions & 0 deletions src/machine/uefi/clock.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
//go:build uefi

package uefi

const microsecondsCalibration = 10000 // 10 milliseconds

// CalibrateTimerFrequency calibrates the timer frequency by measuring
// ticks over a known interval. Returns the timer frequency in ticks
// per microsecond.
func CalibrateTimerFrequency() uint64 {
// Not the most accurate method, but should be good enough for EFI.
start := Ticks()
Stall(microsecondsCalibration)
end := Ticks()

frequency := (end - start) / microsecondsCalibration // ticks per microsecond
if frequency == 0 {
frequency = 1000 // fallback
}
return frequency
}
32 changes: 32 additions & 0 deletions src/machine/uefi/console.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
//go:build uefi

package uefi

import "unsafe"

// SimpleTextOutputProtocol provides text output services.
type SimpleTextOutputProtocol struct {
Reset uintptr
OutputString uintptr
TestString uintptr
QueryMode uintptr
SetMode uintptr
SetAttribute uintptr
ClearScreen uintptr
SetCursorPosition uintptr
EnableCursor uintptr
Mode uintptr
}

// OutputString prints a UCS-2 string to the console.
func OutputString(str *uint16) {
st := GetSystemTable()
if st == nil || st.ConOut == nil || st.ConOut.OutputString == 0 {
return
}
Call(
st.ConOut.OutputString,
uintptr(unsafe.Pointer(st.ConOut)),
uintptr(unsafe.Pointer(str)),
)
}
Loading
Loading