From 139a3a923c3c355fd65000429fee43da762e7f7d Mon Sep 17 00:00:00 2001 From: Matthew Hiles Date: Thu, 23 Jul 2026 13:11:04 -0400 Subject: [PATCH 1/2] add support for UEFI time and UEFI events; fix STOP \n -> \r\n conversion --- src/device/uefi/runtime.go | 21 ++++++++ src/device/uefi/tables.go | 8 +++ src/device/uefi/time.go | 101 ++++++++++++++++++++++++++++++++++++ src/machine/machine_uefi.go | 10 +++- src/runtime/runtime_uefi.go | 12 ++++- 5 files changed, 148 insertions(+), 4 deletions(-) create mode 100644 src/device/uefi/runtime.go create mode 100644 src/device/uefi/time.go diff --git a/src/device/uefi/runtime.go b/src/device/uefi/runtime.go new file mode 100644 index 0000000000..532b02abf3 --- /dev/null +++ b/src/device/uefi/runtime.go @@ -0,0 +1,21 @@ +package uefi + +import _ "unsafe" + +//go:linkname gosched runtime.Gosched +func gosched() + +// WaitForEvent blocks while yielding to the TinyGo scheduler so other +// goroutines can continue to run. +func WaitForEvent(event EFI_EVENT) EFI_STATUS { + for { + status := BS().CheckEvent(event) + if status == EFI_SUCCESS { + return EFI_SUCCESS + } + if status != EFI_NOT_READY { + return status + } + gosched() + } +} diff --git a/src/device/uefi/tables.go b/src/device/uefi/tables.go index 230cbc8005..8eb2bf825b 100644 --- a/src/device/uefi/tables.go +++ b/src/device/uefi/tables.go @@ -20,6 +20,10 @@ type EFI_RUNTIME_SERVICES struct { queryVariableInfo uintptr } +func (p *EFI_RUNTIME_SERVICES) GetTime(time *EFI_TIME, capabilities *EFI_TIME_CAPABILITIES) EFI_STATUS { + return UefiCall2(p.getTime, uintptr(unsafe.Pointer(time)), uintptr(unsafe.Pointer(capabilities))) +} + type EFI_BOOT_SERVICES struct { Hdr EFI_TABLE_HEADER raiseTPL uintptr @@ -82,6 +86,10 @@ func (p *EFI_BOOT_SERVICES) WaitForEvent(numberOfEvents UINTN, event *EFI_EVENT, return UefiCall3(p.waitForEvent, uintptr(numberOfEvents), uintptr(unsafe.Pointer(event)), uintptr(unsafe.Pointer(index))) } +func (p *EFI_BOOT_SERVICES) SignalEvent(event EFI_EVENT) EFI_STATUS { + return UefiCall1(p.signalEvent, uintptr(event)) +} + func (p *EFI_BOOT_SERVICES) CloseEvent(event EFI_EVENT) EFI_STATUS { return UefiCall1(p.closeEvent, uintptr(event)) } diff --git a/src/device/uefi/time.go b/src/device/uefi/time.go new file mode 100644 index 0000000000..35153847e1 --- /dev/null +++ b/src/device/uefi/time.go @@ -0,0 +1,101 @@ +package uefi + +type EFI_TIME struct { + Year uint16 + Month byte + Day byte + Hour byte + Minute byte + Second byte + Pad1 byte + Nanosecond uint32 + TimeZone int16 + Daylight byte + Pad2 byte +} + +type EFI_TIME_CAPABILITIES struct { + Resolution uint32 + Accuracy uint32 + SetsToZero BOOLEAN +} + +func GetTime() (EFI_TIME, EFI_STATUS) { + var time EFI_TIME + status := ST().RuntimeServices.GetTime(&time, nil) + return time, status +} + +func (t *EFI_TIME) GetEpoch() (sec int64, nsec int32) { + year := int(t.Year) + month := int(t.Month) + + d := daysSinceEpoch(year) + d += uint64(daysBefore[month-1]) + if isLeap(year) && month > 2 { + d++ + } + d += uint64(t.Day - 1) + + abs := d * secondsPerDay + abs += uint64(uint64(t.Hour)*uint64(secondsPerHour) + uint64(t.Minute)*uint64(secondsPerMinute) + uint64(t.Second)) + + sec = int64(abs) + (absoluteToInternal + internalToUnix) + nsec = int32(t.Nanosecond) + return +} + +const ( + secondsPerMinute = 60 + secondsPerHour = 60 * secondsPerMinute + secondsPerDay = 24 * secondsPerHour + daysPer400Years = 365*400 + 97 + daysPer100Years = 365*100 + 24 + daysPer4Years = 365*4 + 1 + + absoluteZeroYear = -292277022399 + internalYear = 1 + + absoluteToInternal int64 = (absoluteZeroYear - internalYear) * 365.2425 * secondsPerDay + unixToInternal int64 = (1969*365 + 1969/4 - 1969/100 + 1969/400) * secondsPerDay + internalToUnix int64 = -unixToInternal +) + +var daysBefore = [...]int32{ + 0, + 31, + 31 + 28, + 31 + 28 + 31, + 31 + 28 + 31 + 30, + 31 + 28 + 31 + 30 + 31, + 31 + 28 + 31 + 30 + 31 + 30, + 31 + 28 + 31 + 30 + 31 + 30 + 31, + 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31, + 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30, + 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31, + 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30, + 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30 + 31, +} + +func daysSinceEpoch(year int) uint64 { + y := uint64(int64(year) - absoluteZeroYear) + + n := y / 400 + y -= 400 * n + d := daysPer400Years * n + + n = y / 100 + y -= 100 * n + d += daysPer100Years * n + + n = y / 4 + y -= 4 * n + d += daysPer4Years * n + + d += 365 * y + return d +} + +func isLeap(year int) bool { + return year%4 == 0 && (year%100 != 0 || year%400 == 0) +} diff --git a/src/machine/machine_uefi.go b/src/machine/machine_uefi.go index 94659d2b3e..9d655f0f43 100644 --- a/src/machine/machine_uefi.go +++ b/src/machine/machine_uefi.go @@ -9,8 +9,10 @@ import ( const deviceName = "UEFI" type ( - EFI_STATUS = deviceuefi.EFI_STATUS - TextOutput = deviceuefi.TextOutput + EFI_STATUS = deviceuefi.EFI_STATUS + EFI_TIME = deviceuefi.EFI_TIME + EFI_TIME_CAPABILITIES = deviceuefi.EFI_TIME_CAPABILITIES + TextOutput = deviceuefi.TextOutput Error = deviceuefi.Error ) @@ -88,6 +90,10 @@ var ( ErrHTTPError = deviceuefi.ErrHTTPError ) +func GetTime() (EFI_TIME, EFI_STATUS) { + return deviceuefi.GetTime() +} + func ConsoleOut() *TextOutput { return deviceuefi.ConsoleOut() } diff --git a/src/runtime/runtime_uefi.go b/src/runtime/runtime_uefi.go index c97ab748eb..9104adb82a 100644 --- a/src/runtime/runtime_uefi.go +++ b/src/runtime/runtime_uefi.go @@ -56,9 +56,8 @@ func sleepTicks(d timeUnit) { func putchar(c byte) { if c == '\n' { - buf := [4]uefi.CHAR16{'\r', 0, '\n', 0} + buf := [2]uefi.CHAR16{uefi.CHAR16('\r'), 0} uefi.ST().ConOut.OutputString(&buf[0]) - return } buf := [2]uefi.CHAR16{uefi.CHAR16(c), 0} uefi.ST().ConOut.OutputString(&buf[0]) @@ -103,6 +102,15 @@ func growHeap() bool { return false } +func init() { + mono := nanotime() + efiTime, status := uefi.GetTime() + if status == uefi.EFI_SUCCESS { + sec, nsec := efiTime.GetEpoch() + timeOffset.Store(sec*1000000000 + int64(nsec) - mono) + } +} + func SetWaitForEvents(f func()) { waitForEventsFunction = f } From c7fa0364d86d8da680856805e40c9f8dc6713c52 Mon Sep 17 00:00:00 2001 From: Matthew Hiles Date: Thu, 23 Jul 2026 20:20:09 -0400 Subject: [PATCH 2/2] make it so both scheduler=none and scheduler=tasks works --- compileopts/config.go | 6 +- src/internal/task/task_stack_amd64.go | 2 +- src/internal/task/task_stack_amd64_winabi.go | 58 ++++++++++++++++++++ src/runtime/sleep_custom_uefi.go | 14 +++++ targets/uefi-amd64.json | 2 +- 5 files changed, 79 insertions(+), 3 deletions(-) create mode 100644 src/internal/task/task_stack_amd64_winabi.go create mode 100644 src/runtime/sleep_custom_uefi.go diff --git a/compileopts/config.go b/compileopts/config.go index 7786cd2178..1c38078fae 100644 --- a/compileopts/config.go +++ b/compileopts/config.go @@ -482,7 +482,11 @@ func (c *Config) LinkerFlavor() string { // ExtraFiles returns the list of extra files to be built and linked with the // executable. This can include extra C and assembly files. func (c *Config) ExtraFiles() []string { - return c.Target.ExtraFiles + files := append([]string(nil), c.Target.ExtraFiles...) + if c.Scheduler() == "tasks" && c.GOARCH() == "amd64" && slices.Contains(c.Target.BuildTags, "uefi") { + files = append(files, "src/internal/task/task_stack_amd64_windows.S") + } + return files } // DumpSSA returns whether to dump Go SSA while compiling (-dumpssa flag). Only diff --git a/src/internal/task/task_stack_amd64.go b/src/internal/task/task_stack_amd64.go index d252b1c50d..bfd18b5758 100644 --- a/src/internal/task/task_stack_amd64.go +++ b/src/internal/task/task_stack_amd64.go @@ -1,4 +1,4 @@ -//go:build scheduler.tasks && amd64 && !windows +//go:build scheduler.tasks && amd64 && !windows && !uefi package task diff --git a/src/internal/task/task_stack_amd64_winabi.go b/src/internal/task/task_stack_amd64_winabi.go new file mode 100644 index 0000000000..3b9c2e22db --- /dev/null +++ b/src/internal/task/task_stack_amd64_winabi.go @@ -0,0 +1,58 @@ +//go:build scheduler.tasks && amd64 && uefi + +package task + +// This is almost the same as task_stack_amd64.go, but with the extra rdi and +// rsi registers saved: UEFI on amd64 uses the Win64 ABI. + +import "unsafe" + +var systemStack uintptr + +// calleeSavedRegs is the list of registers that must be saved and restored when +// switching between tasks. Also see task_stack_amd64_windows.S that relies on +// the exact layout of this struct. +type calleeSavedRegs struct { + // rbx is placed here so the stack is correctly aligned when saving XMM regs. + rbx uintptr + xmm15 [2]uint64 + xmm14 [2]uint64 + xmm13 [2]uint64 + xmm12 [2]uint64 + xmm11 [2]uint64 + xmm10 [2]uint64 + xmm9 [2]uint64 + xmm8 [2]uint64 + xmm7 [2]uint64 + xmm6 [2]uint64 + rbp uintptr + rdi uintptr + rsi uintptr + r12 uintptr + r13 uintptr + r14 uintptr + r15 uintptr + + pc uintptr +} + +func (s *state) archInit(r *calleeSavedRegs, fn uintptr, args unsafe.Pointer) { + s.sp = uintptr(unsafe.Pointer(r)) + r.pc = uintptr(unsafe.Pointer(&startTask)) + r.r12 = fn + r.r13 = uintptr(args) +} + +func (s *state) resume() { + swapTask(s.sp, &systemStack) +} + +func (s *state) pause() { + newStack := systemStack + systemStack = 0 + swapTask(newStack, &s.sp) +} + +func SystemStack() uintptr { + return systemStack +} diff --git a/src/runtime/sleep_custom_uefi.go b/src/runtime/sleep_custom_uefi.go new file mode 100644 index 0000000000..784f3b4f28 --- /dev/null +++ b/src/runtime/sleep_custom_uefi.go @@ -0,0 +1,14 @@ +//go:build scheduler.tasks && uefi + +package runtime + +//go:linkname gosched runtime.Gosched +func gosched() + +func schedulerSleepCustom(duration int64) bool { + deadline := ticks() + nanosecondsToTicks(duration) + for ticks() < deadline { + gosched() + } + return true +} diff --git a/targets/uefi-amd64.json b/targets/uefi-amd64.json index c594aecce6..da1f5943a3 100644 --- a/targets/uefi-amd64.json +++ b/targets/uefi-amd64.json @@ -6,7 +6,7 @@ "goos": "linux", "goarch": "amd64", "gc": "leaking", - "scheduler": "none", + "scheduler": "tasks", "linker": "ld.lld", "linker-flavor": "coff", "libc": "picolibc",