targets: minimal uefi target support (#5452)

* runtime: split baremetal memory setup

* runtime: extract Windows PE globals scan helper

* compileopts,builder: add target linker flavor override

* machine,runtime,targets: add minimal uefi-amd64 target

* runtime,examples: add clean UEFI exit path test

* machine,x86: fix amd64 ABI stack handling

* machine/uefi: add CHAR16 conversion helpers

* machine/uefi: add loaded image protocol support

* runtime: add UEFI PE globals support

* machine,runtime,x86: add UEFI time and text output support

* machine,runtime,x86: add UEFI text and time support

* machine,runtime,examples: add UEFI text input support

* machine,examples: add UEFI graphics output support

* add rest of STOP methods (direct UEFI ABI only)

* runtime: fix UEFI sleep tick conversion

* machine/uefi: make text input waits cooperative

* address TestClangAttributes/uefi-amd64 failure

* address TestConfigLinkerFlavor bug

* uefi: move raw bindings to device package

* strip down implementation to bare minimum

* amd64: rename x86 package

* do git restore upstream/dev for src/net

* add smoketest for uefi-amd64; add required zeroSizeAllocPtr constant

* address PR issues: remove unused files; make putchar() insert '\r' before '\n'

* swap lines around
This commit is contained in:
Matthew Hiles
2026-07-23 08:34:18 -04:00
committed by GitHub
parent 3797e89600
commit 9c2eafce6f
13 changed files with 318 additions and 5 deletions
+2
View File
@@ -657,6 +657,8 @@ smoketest: testchdir
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pico2-ice examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -o test.efi -target=uefi-amd64 examples/test
@$(MD5SUM) test.efi
# test simulated boards on play.tinygo.org
ifneq ($(WASM), 0)
GOOS=js GOARCH=wasm $(TINYGO) build -size short -o test.wasm -tags=arduino_uno examples/blinky1
+1
View File
@@ -37,6 +37,7 @@ func TestClangAttributes(t *testing.T) {
"nintendoswitch",
"riscv-qemu",
"tkey",
"uefi-amd64",
"wasip1",
"wasip2",
"wasm",
+111
View File
@@ -0,0 +1,111 @@
//go:build uefi
package machine
import (
deviceuefi "device/uefi"
)
const deviceName = "UEFI"
type (
EFI_STATUS = deviceuefi.EFI_STATUS
TextOutput = deviceuefi.TextOutput
Error = deviceuefi.Error
)
const (
EFI_SUCCESS = deviceuefi.EFI_SUCCESS
EFI_LOAD_ERROR = deviceuefi.EFI_LOAD_ERROR
EFI_INVALID_PARAMETER = deviceuefi.EFI_INVALID_PARAMETER
EFI_UNSUPPORTED = deviceuefi.EFI_UNSUPPORTED
EFI_BAD_BUFFER_SIZE = deviceuefi.EFI_BAD_BUFFER_SIZE
EFI_BUFFER_TOO_SMALL = deviceuefi.EFI_BUFFER_TOO_SMALL
EFI_NOT_READY = deviceuefi.EFI_NOT_READY
EFI_DEVICE_ERROR = deviceuefi.EFI_DEVICE_ERROR
EFI_WRITE_PROTECTED = deviceuefi.EFI_WRITE_PROTECTED
EFI_OUT_OF_RESOURCES = deviceuefi.EFI_OUT_OF_RESOURCES
EFI_VOLUME_CORRUPTED = deviceuefi.EFI_VOLUME_CORRUPTED
EFI_VOLUME_FULL = deviceuefi.EFI_VOLUME_FULL
EFI_NO_MEDIA = deviceuefi.EFI_NO_MEDIA
EFI_MEDIA_CHANGED = deviceuefi.EFI_MEDIA_CHANGED
EFI_NOT_FOUND = deviceuefi.EFI_NOT_FOUND
EFI_ACCESS_DENIED = deviceuefi.EFI_ACCESS_DENIED
EFI_NO_RESPONSE = deviceuefi.EFI_NO_RESPONSE
EFI_NO_MAPPING = deviceuefi.EFI_NO_MAPPING
EFI_TIMEOUT = deviceuefi.EFI_TIMEOUT
EFI_NOT_STARTED = deviceuefi.EFI_NOT_STARTED
EFI_ALREADY_STARTED = deviceuefi.EFI_ALREADY_STARTED
EFI_ABORTED = deviceuefi.EFI_ABORTED
EFI_ICMP_ERROR = deviceuefi.EFI_ICMP_ERROR
EFI_TFTP_ERROR = deviceuefi.EFI_TFTP_ERROR
EFI_PROTOCOL_ERROR = deviceuefi.EFI_PROTOCOL_ERROR
EFI_INCOMPATIBLE_VERSION = deviceuefi.EFI_INCOMPATIBLE_VERSION
EFI_SECURITY_VIOLATION = deviceuefi.EFI_SECURITY_VIOLATION
EFI_CRC_ERROR = deviceuefi.EFI_CRC_ERROR
EFI_END_OF_MEDIA = deviceuefi.EFI_END_OF_MEDIA
EFI_END_OF_FILE = deviceuefi.EFI_END_OF_FILE
EFI_INVALID_LANGUAGE = deviceuefi.EFI_INVALID_LANGUAGE
EFI_COMPROMISED_DATA = deviceuefi.EFI_COMPROMISED_DATA
EFI_IP_ADDRESS_CONFLICT = deviceuefi.EFI_IP_ADDRESS_CONFLICT
EFI_HTTP_ERROR = deviceuefi.EFI_HTTP_ERROR
)
var (
ErrLoadError = deviceuefi.ErrLoadError
ErrInvalidParameter = deviceuefi.ErrInvalidParameter
ErrUnsupported = deviceuefi.ErrUnsupported
ErrBadBufferSize = deviceuefi.ErrBadBufferSize
ErrBufferTooSmall = deviceuefi.ErrBufferTooSmall
ErrNotReady = deviceuefi.ErrNotReady
ErrDeviceError = deviceuefi.ErrDeviceError
ErrWriteProtected = deviceuefi.ErrWriteProtected
ErrOutOfResources = deviceuefi.ErrOutOfResources
ErrVolumeCorrupted = deviceuefi.ErrVolumeCorrupted
ErrVolumeFull = deviceuefi.ErrVolumeFull
ErrNoMedia = deviceuefi.ErrNoMedia
ErrMediaChanged = deviceuefi.ErrMediaChanged
ErrNotFound = deviceuefi.ErrNotFound
ErrAccessDenied = deviceuefi.ErrAccessDenied
ErrNoResponse = deviceuefi.ErrNoResponse
ErrNoMapping = deviceuefi.ErrNoMapping
ErrTimeout = deviceuefi.ErrTimeout
ErrNotStarted = deviceuefi.ErrNotStarted
ErrAlreadyStarted = deviceuefi.ErrAlreadyStarted
ErrAborted = deviceuefi.ErrAborted
ErrICMPError = deviceuefi.ErrICMPError
ErrTFTPError = deviceuefi.ErrTFTPError
ErrProtocolError = deviceuefi.ErrProtocolError
ErrIncompatibleVersion = deviceuefi.ErrIncompatibleVersion
ErrSecurityViolation = deviceuefi.ErrSecurityViolation
ErrCRCError = deviceuefi.ErrCRCError
ErrEndOfMedia = deviceuefi.ErrEndOfMedia
ErrEndOfFile = deviceuefi.ErrEndOfFile
ErrInvalidLanguage = deviceuefi.ErrInvalidLanguage
ErrCompromisedData = deviceuefi.ErrCompromisedData
ErrIPAddressConflict = deviceuefi.ErrIPAddressConflict
ErrHTTPError = deviceuefi.ErrHTTPError
)
func ConsoleOut() *TextOutput {
return deviceuefi.ConsoleOut()
}
func StandardError() *TextOutput {
return deviceuefi.StandardError()
}
func StatusError(status EFI_STATUS) *Error {
return deviceuefi.StatusError(status)
}
func (Pin) Configure(PinConfig) {
}
func (Pin) Set(bool) {
}
func (Pin) Get() bool {
return false
}
+1 -1
View File
@@ -1,4 +1,4 @@
//go:build baremetal
//go:build baremetal && !uefi
package runtime
+1 -1
View File
@@ -1,4 +1,4 @@
//go:build baremetal || tinygo.wasm
//go:build (baremetal || tinygo.wasm) && !uefi
package runtime
+9
View File
@@ -0,0 +1,9 @@
//go:build gc.leaking && uefi
package runtime
import _ "unsafe"
//go:export tinygo_scanstack
func tinygo_scanstack() {
}
+1 -1
View File
@@ -1,4 +1,4 @@
//go:build !baremetal || tkey
//go:build !baremetal || tkey || uefi
package interrupt
+1 -1
View File
@@ -1,4 +1,4 @@
//go:build windows
//go:build windows || uefi
package runtime
+139
View File
@@ -0,0 +1,139 @@
//go:build uefi
package runtime
import "device/uefi"
const zeroSizeAllocPtr uintptr = 16
//go:linkname procPin sync/atomic.runtime_procPin
func procPin() {
}
//go:linkname procUnpin sync/atomic.runtime_procUnpin
func procUnpin() {
}
var heapSize uintptr = 64 * 1024 * 1024
var heapStart, heapEnd uintptr
var stackTop uintptr
var allocatePagesAddress uefi.EFI_PHYSICAL_ADDRESS
var waitForEventsFunction = func() {
uefi.CpuPause()
}
func ticks() timeUnit {
return timeUnit(uefi.Ticks())
}
func nanosecondsToTicks(ns int64) timeUnit {
frequency := int64(uefi.TicksFrequency())
if frequency == 0 {
return timeUnit(ns)
}
seconds := ns / 1000000000
remainder := ns % 1000000000
return timeUnit(seconds*frequency + (remainder*frequency)/1000000000)
}
func ticksToNanoseconds(t timeUnit) int64 {
frequency := int64(uefi.TicksFrequency())
if frequency == 0 {
return int64(t)
}
return int64(t) * 1000000000 / frequency
}
func sleepTicks(d timeUnit) {
if d == 0 {
return
}
end := ticks() + d
for ticks() < end {
uefi.CpuPause()
}
}
func putchar(c byte) {
if c == '\n' {
buf := [4]uefi.CHAR16{'\r', 0, '\n', 0}
uefi.ST().ConOut.OutputString(&buf[0])
return
}
buf := [2]uefi.CHAR16{uefi.CHAR16(c), 0}
uefi.ST().ConOut.OutputString(&buf[0])
}
func exit(code int) {
uefi.BS().Exit(uefi.GetImageHandle(), uefi.EFI_STATUS(code), 0, nil)
}
func abort() {
uefi.BS().Exit(uefi.GetImageHandle(), uefi.EFI_ABORTED, 0, nil)
}
func preinit() {
uefi.BS().SetWatchdogTimer(0, 0, 0, nil)
if !growHeap() {
runtimePanic("could not allocate initial UEFI heap")
}
}
func growHeap() bool {
newHeapSize := ((heapSize * 4) / 3) &^ 4095
for newHeapSize >= heapSize {
pages := newHeapSize / 4096
status := uefi.BS().AllocatePages(
uefi.AllocateAnyPages,
uefi.EfiLoaderData,
uefi.UINTN(pages),
&allocatePagesAddress,
)
if status == uefi.EFI_SUCCESS {
heapStart = uintptr(allocatePagesAddress)
heapSize = newHeapSize
setHeapEnd(heapStart + heapSize)
return true
}
if status != uefi.EFI_OUT_OF_RESOURCES {
return false
}
newHeapSize /= 2
}
return false
}
func SetWaitForEvents(f func()) {
waitForEventsFunction = f
}
func waitForEvents() {
waitForEventsFunction()
}
//go:noinline
func runMain() {
run()
}
func buffered() int {
return 0
}
func getchar() byte {
return 0
}
//export efi_main
func main(imageHandle uintptr, systemTable uintptr) uintptr {
uefi.Init(imageHandle, systemTable)
preinit()
stackTop = getCurrentStackPointer()
runMain()
if heapStart != 0 {
uefi.BS().FreePages(uefi.EFI_PHYSICAL_ADDRESS(heapStart), uefi.UINTN(heapSize/4096))
}
return 0
}
+3
View File
@@ -248,6 +248,9 @@ func sleep(duration int64) {
if duration <= 0 {
return
}
if schedulerSleepCustom(duration) {
return
}
addSleepTask(task.Current(), nanosecondsToTicks(duration))
task.Pause()
+7
View File
@@ -0,0 +1,7 @@
//go:build !(scheduler.tasks && uefi)
package runtime
func schedulerSleepCustom(duration int64) bool {
return false
}
+1 -1
View File
@@ -1,4 +1,4 @@
//go:build !tinygo.riscv && !cortexm && !(linux && !baremetal && !tinygo.wasm && !nintendoswitch) && !darwin && !wasip1
//go:build !tinygo.riscv && !cortexm && !(linux && !baremetal && !tinygo.wasm && !nintendoswitch) && !darwin && !wasip1 && !uefi
package runtime
+41
View File
@@ -0,0 +1,41 @@
{
"build-tags": ["uefi", "baremetal", "linux", "amd64"],
"llvm-target": "x86_64-unknown-windows-gnu",
"cpu": "x86-64",
"features": "+cmov,+cx8,+fxsr,+mmx,+sse,+sse2,+x87",
"goos": "linux",
"goarch": "amd64",
"gc": "leaking",
"scheduler": "none",
"linker": "ld.lld",
"linker-flavor": "coff",
"libc": "picolibc",
"automatic-stack-size": false,
"default-stack-size": 65536,
"cflags": [
"-Werror",
"-fshort-enums",
"-fomit-frame-pointer",
"-fno-exceptions", "-fno-unwind-tables", "-fno-asynchronous-unwind-tables",
"-ffunction-sections", "-fdata-sections",
"-ffreestanding",
"-fshort-wchar",
"-mno-red-zone"
],
"ldflags": [
"-m", "i386pep",
"--image-base", "0x400000",
"--entry", "efi_main",
"--subsystem", "efi_application",
"-Bdynamic",
"--gc-sections",
"--no-insert-timestamp",
"--no-dynamicbase"
],
"extra-files": [
"src/device/amd64/cpu_amd64.S",
"src/device/uefi/asm_amd64.S",
"src/runtime/asm_amd64_windows.S"
],
"gdb": ["gdb-multiarch", "gdb"]
}