diff --git a/builder/build.go b/builder/build.go index 4987fdddc6..2c2048e5a5 100644 --- a/builder/build.go +++ b/builder/build.go @@ -842,22 +842,25 @@ 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" { - // Options for the MinGW wrapper for the lld COFF linker. + switch config.LinkerFlavor() { + case "coff": + // Options for driving ld.lld in PE/COFF mode. ldflags = append(ldflags, "-Xlink=/opt:lldlto="+strconv.Itoa(speedLevel), "--thinlto-cache-dir="+filepath.Join(cacheDir, "thinlto")) - } else if config.GOOS() == "darwin" { + case "darwin": // Options for the ld64-compatible lld linker. ldflags = append(ldflags, "--lto-O"+strconv.Itoa(speedLevel), "-cache_path_lto", filepath.Join(cacheDir, "thinlto")) - } else { + case "gnu": // Options for the ELF linker. ldflags = append(ldflags, "--lto-O"+strconv.Itoa(speedLevel), "--thinlto-cache-dir="+filepath.Join(cacheDir, "thinlto"), ) + default: + return fmt.Errorf("unknown linker flavor: %s", config.LinkerFlavor()) } if config.CodeModel() != "default" { ldflags = append(ldflags, diff --git a/builder/builder_test.go b/builder/builder_test.go index 8e62d9183c..941ed3251a 100644 --- a/builder/builder_test.go +++ b/builder/builder_test.go @@ -35,6 +35,7 @@ func TestClangAttributes(t *testing.T) { "nintendoswitch", "riscv-qemu", "tkey", + "uefi-amd64", "wasip1", "wasip2", "wasm", diff --git a/compileopts/config.go b/compileopts/config.go index 673de40f95..a007c3f4aa 100644 --- a/compileopts/config.go +++ b/compileopts/config.go @@ -466,6 +466,26 @@ func (c *Config) LDFlags() []string { return ldflags } +// LinkerFlavor returns how the configured linker should be driven. +// Usually this is derived from GOOS, but targets may override it explicitly. +func (c *Config) LinkerFlavor() string { + if c.Target.LinkerFlavor != "" { + return c.Target.LinkerFlavor + } + goos := c.GOOS() + if goos == "" { + goos = c.Options.GOOS + } + switch goos { + case "windows": + return "coff" + case "darwin": + return "darwin" + default: + return "gnu" + } +} + // 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 { diff --git a/compileopts/target.go b/compileopts/target.go index 70c7047462..42e3734870 100644 --- a/compileopts/target.go +++ b/compileopts/target.go @@ -38,7 +38,8 @@ type TargetSpec struct { Scheduler string `json:"scheduler,omitempty"` Serial string `json:"serial,omitempty"` // which serial output to use (uart, usb, none) Linker string `json:"linker,omitempty"` - RTLib string `json:"rtlib,omitempty"` // compiler runtime library (libgcc, compiler-rt) + LinkerFlavor string `json:"linker-flavor,omitempty"` // how to drive the configured linker (for example: gnu, coff, darwin) + RTLib string `json:"rtlib,omitempty"` // compiler runtime library (libgcc, compiler-rt) Libc string `json:"libc,omitempty"` AutoStackSize *bool `json:"automatic-stack-size,omitempty"` // Determine stack size automatically at compile time. DefaultStackSize uint64 `json:"default-stack-size,omitempty"` // Default stack size if the size couldn't be determined at compile time. diff --git a/compileopts/target_test.go b/compileopts/target_test.go index d8a17a5e34..c700418839 100644 --- a/compileopts/target_test.go +++ b/compileopts/target_test.go @@ -112,3 +112,51 @@ func TestOverrideProperties(t *testing.T) { } } + +func TestConfigLinkerFlavor(t *testing.T) { + tests := []struct { + name string + target *TargetSpec + goos string + want string + }{ + { + name: "default gnu", + target: &TargetSpec{}, + goos: "linux", + want: "gnu", + }, + { + name: "default coff", + target: &TargetSpec{}, + goos: "windows", + want: "coff", + }, + { + name: "default darwin", + target: &TargetSpec{}, + goos: "darwin", + want: "darwin", + }, + { + name: "target override", + target: &TargetSpec{ + LinkerFlavor: "coff", + }, + goos: "linux", + want: "coff", + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + config := &Config{ + Options: &Options{GOOS: tc.goos}, + Target: tc.target, + } + if got := config.LinkerFlavor(); got != tc.want { + t.Fatalf("LinkerFlavor() = %q, want %q", got, tc.want) + } + }) + } +} diff --git a/examples/uefi-exit/main.go b/examples/uefi-exit/main.go new file mode 100644 index 0000000000..da29a2cadf --- /dev/null +++ b/examples/uefi-exit/main.go @@ -0,0 +1,4 @@ +package main + +func main() { +} diff --git a/src/device/amd64/cpu.go b/src/device/amd64/cpu.go new file mode 100644 index 0000000000..32cbfe798c --- /dev/null +++ b/src/device/amd64/cpu.go @@ -0,0 +1,74 @@ +//go:build amd64 + +package amd64 + +const ( + CPUIDTimeStampCounter = 0x15 + CPUIDProcessorFrequency = 0x16 +) + +type CPUExtendedFamily uint16 + +const ( + CPUFamilyIntelCore CPUExtendedFamily = 6 +) + +//export asmPause +func AsmPause() + +//export asmReadRdtsc +func AsmReadRdtsc() uint64 + +//export asmCpuid +func AsmCpuid(index uint32, registerEax *uint32, registerEbx *uint32, registerEcx *uint32) int + +var maxCpuidIndex uint32 +var stdVendorName0 uint32 +var stdCpuid1Eax uint32 + +func init() { + AsmCpuid(0, &maxCpuidIndex, &stdVendorName0, nil) + AsmCpuid(1, &stdCpuid1Eax, nil, nil) +} + +func getExtendedCPUFamily() CPUExtendedFamily { + family := CPUExtendedFamily((stdCpuid1Eax >> 8) & 0x0f) + family += CPUExtendedFamily((stdCpuid1Eax >> 20) & 0xff) + return family +} + +func isIntel() bool { + return stdVendorName0 == 0x756e6547 +} + +func isIntelFamilyCore() bool { + return isIntel() && getExtendedCPUFamily() == CPUFamilyIntelCore +} + +func InternalGetPerformanceCounterFrequency() uint64 { + if maxCpuidIndex >= CPUIDTimeStampCounter { + return cpuidCoreClockCalculateTSCFrequency() + } + return 0 +} + +func cpuidCoreClockCalculateTSCFrequency() uint64 { + var eax uint32 + var ebx uint32 + var ecx uint32 + + AsmCpuid(CPUIDTimeStampCounter, &eax, &ebx, &ecx) + if eax == 0 || ebx == 0 { + return 0 + } + + coreCrystalFrequency := uint64(ecx) + if coreCrystalFrequency == 0 { + if !isIntelFamilyCore() { + return 0 + } + coreCrystalFrequency = 24000000 + } + + return ((coreCrystalFrequency * uint64(ebx)) + (uint64(eax) / 2)) / uint64(eax) +} diff --git a/src/device/amd64/cpu_amd64.S b/src/device/amd64/cpu_amd64.S new file mode 100644 index 0000000000..ce1990c4cf --- /dev/null +++ b/src/device/amd64/cpu_amd64.S @@ -0,0 +1,39 @@ +.section .text + +.global asmPause +asmPause: + pause + ret + +.global asmReadRdtsc +asmReadRdtsc: + rdtsc + shlq $0x20, %rdx + orq %rdx, %rax + ret + +.global asmCpuid +asmCpuid: + pushq %rbx + + mov %ecx, %eax + pushq %rax + + pushq %rdx + cpuid + + test %r9, %r9 + jz .SkipEcx + mov %ecx, (%r9) +.SkipEcx: + popq %rcx + jrcxz .SkipEax + mov %eax, (%rcx) +.SkipEax: + mov %r8, %rcx + jrcxz .SkipEbx + mov %ebx, (%rcx) +.SkipEbx: + popq %rax + popq %rbx + ret diff --git a/src/device/uefi/arch_x86.go b/src/device/uefi/arch_x86.go new file mode 100644 index 0000000000..3eb344fe94 --- /dev/null +++ b/src/device/uefi/arch_x86.go @@ -0,0 +1,17 @@ +//go:build i386 || amd64 + +package uefi + +import "device/amd64" + +func Ticks() uint64 { + return amd64.AsmReadRdtsc() +} + +func CpuPause() { + amd64.AsmPause() +} + +func getTSCFrequency() uint64 { + return amd64.InternalGetPerformanceCounterFrequency() +} diff --git a/src/device/uefi/asm_amd64.S b/src/device/uefi/asm_amd64.S new file mode 100644 index 0000000000..5037432991 --- /dev/null +++ b/src/device/uefi/asm_amd64.S @@ -0,0 +1,201 @@ +.section .text + +.global uefiCall0 +uefiCall0: + pushq %rbp + movq %rsp, %rbp + subq $0x20, %rsp + callq *%rcx + movq %rbp, %rsp + popq %rbp + ret + +.global uefiCall1 +uefiCall1: + pushq %rbp + movq %rsp, %rbp + subq $0x20, %rsp + movq %rcx, %rax + movq %rdx, %rcx + callq *%rax + movq %rbp, %rsp + popq %rbp + ret + +.global uefiCall2 +uefiCall2: + pushq %rbp + movq %rsp, %rbp + subq $0x20, %rsp + movq %rcx, %rax + movq %rdx, %rcx + movq %r8, %rdx + callq *%rax + movq %rbp, %rsp + popq %rbp + ret + +.global uefiCall3 +uefiCall3: + pushq %rbp + movq %rsp, %rbp + subq $0x20, %rsp + movq %rcx, %rax + movq %rdx, %rcx + movq %r8, %rdx + movq %r9, %r8 + callq *%rax + movq %rbp, %rsp + popq %rbp + ret + +.global uefiCall4 +uefiCall4: + pushq %rbp + movq %rsp, %rbp + subq $0x20, %rsp + movq %rcx, %rax + movq %rdx, %rcx + movq %r8, %rdx + movq %r9, %r8 + movq 0x30(%rbp), %r9 + callq *%rax + movq %rbp, %rsp + popq %rbp + ret + +.global uefiCall5 +uefiCall5: + pushq %rbp + movq %rsp, %rbp + subq $0x30, %rsp + movq %rcx, %rax + movq %rdx, %rcx + movq %r8, %rdx + movq %r9, %r8 + movq 0x30(%rbp), %r9 + movq 0x38(%rbp), %r10 + movq %r10, 0x20(%rsp) + callq *%rax + movq %rbp, %rsp + popq %rbp + ret + +.global uefiCall6 +uefiCall6: + pushq %rbp + movq %rsp, %rbp + subq $0x30, %rsp + movq %rcx, %rax + movq %rdx, %rcx + movq %r8, %rdx + movq %r9, %r8 + movq 0x30(%rbp), %r9 + movq 0x38(%rbp), %r10 + movq %r10, 0x20(%rsp) + movq 0x40(%rbp), %r10 + movq %r10, 0x28(%rsp) + callq *%rax + movq %rbp, %rsp + popq %rbp + ret + +.global uefiCall7 +uefiCall7: + pushq %rbp + movq %rsp, %rbp + subq $0x40, %rsp + movq %rcx, %rax + movq %rdx, %rcx + movq %r8, %rdx + movq %r9, %r8 + movq 0x30(%rbp), %r9 + movq 0x38(%rbp), %r10 + movq %r10, 0x20(%rsp) + movq 0x40(%rbp), %r10 + movq %r10, 0x28(%rsp) + movq 0x48(%rbp), %r10 + movq %r10, 0x30(%rsp) + callq *%rax + movq %rbp, %rsp + popq %rbp + ret + +.global uefiCall8 +uefiCall8: + pushq %rbp + movq %rsp, %rbp + subq $0x40, %rsp + movq %rcx, %rax + movq %rdx, %rcx + movq %r8, %rdx + movq %r9, %r8 + movq 0x30(%rbp), %r9 + movq 0x38(%rbp), %r10 + movq %r10, 0x20(%rsp) + movq 0x40(%rbp), %r10 + movq %r10, 0x28(%rsp) + movq 0x48(%rbp), %r10 + movq %r10, 0x30(%rsp) + movq 0x50(%rbp), %r10 + movq %r10, 0x38(%rsp) + callq *%rax + movq %rbp, %rsp + popq %rbp + ret + +.global uefiCall9 +uefiCall9: + pushq %rbp + movq %rsp, %rbp + subq $0x50, %rsp + movq %rcx, %rax + movq %rdx, %rcx + movq %r8, %rdx + movq %r9, %r8 + movq 0x30(%rbp), %r9 + movq 0x38(%rbp), %r10 + movq %r10, 0x20(%rsp) + movq 0x40(%rbp), %r10 + movq %r10, 0x28(%rsp) + movq 0x48(%rbp), %r10 + movq %r10, 0x30(%rsp) + movq 0x50(%rbp), %r10 + movq %r10, 0x38(%rsp) + movq 0x58(%rbp), %r10 + movq %r10, 0x40(%rsp) + callq *%rax + movq %rbp, %rsp + popq %rbp + ret + +.global uefiCall10 +uefiCall10: + pushq %rbp + movq %rsp, %rbp + subq $0x50, %rsp + movq %rcx, %rax + movq %rdx, %rcx + movq %r8, %rdx + movq %r9, %r8 + movq 0x30(%rbp), %r9 + movq 0x38(%rbp), %r10 + movq %r10, 0x20(%rsp) + movq 0x40(%rbp), %r10 + movq %r10, 0x28(%rsp) + movq 0x48(%rbp), %r10 + movq %r10, 0x30(%rsp) + movq 0x50(%rbp), %r10 + movq %r10, 0x38(%rsp) + movq 0x58(%rbp), %r10 + movq %r10, 0x40(%rsp) + movq 0x60(%rbp), %r10 + movq %r10, 0x48(%rsp) + callq *%rax + movq %rbp, %rsp + popq %rbp + ret + +.global ___chkstk_ms +___chkstk_ms: + ret diff --git a/src/device/uefi/call.go b/src/device/uefi/call.go new file mode 100644 index 0000000000..0ae162fce5 --- /dev/null +++ b/src/device/uefi/call.go @@ -0,0 +1,45 @@ +package uefi + +//go:nosplit +//export uefiCall0 +func UefiCall0(fn uintptr) EFI_STATUS + +//go:nosplit +//go:export uefiCall1 +func UefiCall1(fn uintptr, a uintptr) EFI_STATUS + +//go:nosplit +//go:export uefiCall2 +func UefiCall2(fn uintptr, a uintptr, b uintptr) EFI_STATUS + +//go:nosplit +//go:export uefiCall3 +func UefiCall3(fn uintptr, a uintptr, b uintptr, c uintptr) EFI_STATUS + +//go:nosplit +//go:export uefiCall4 +func UefiCall4(fn uintptr, a uintptr, b uintptr, c uintptr, d uintptr) EFI_STATUS + +//go:nosplit +//go:export uefiCall5 +func UefiCall5(fn uintptr, a uintptr, b uintptr, c uintptr, d uintptr, e uintptr) EFI_STATUS + +//go:nosplit +//go:export uefiCall6 +func UefiCall6(fn uintptr, a uintptr, b uintptr, c uintptr, d uintptr, e uintptr, f uintptr) EFI_STATUS + +//go:nosplit +//go:export uefiCall7 +func UefiCall7(fn uintptr, a uintptr, b uintptr, c uintptr, d uintptr, e uintptr, f uintptr, g uintptr) EFI_STATUS + +//go:nosplit +//go:export uefiCall8 +func UefiCall8(fn uintptr, a uintptr, b uintptr, c uintptr, d uintptr, e uintptr, f uintptr, g uintptr, h uintptr) EFI_STATUS + +//go:nosplit +//go:export uefiCall9 +func UefiCall9(fn uintptr, a uintptr, b uintptr, c uintptr, d uintptr, e uintptr, f uintptr, g uintptr, h uintptr, i uintptr) EFI_STATUS + +//go:nosplit +//go:export uefiCall10 +func UefiCall10(fn uintptr, a uintptr, b uintptr, c uintptr, d uintptr, e uintptr, f uintptr, g uintptr, h uintptr, i uintptr, j uintptr) EFI_STATUS diff --git a/src/device/uefi/char16.go b/src/device/uefi/char16.go new file mode 100644 index 0000000000..3c3818c7c1 --- /dev/null +++ b/src/device/uefi/char16.go @@ -0,0 +1,77 @@ +package uefi + +import ( + "unicode/utf16" + "unsafe" +) + +// StringToCHAR16 converts a Go string to a UTF-16 code unit slice. +func StringToCHAR16(s string) []CHAR16 { + if s == "" { + return nil + } + + encoded := utf16.Encode([]rune(s)) + out := make([]CHAR16, len(encoded)) + for i, r := range encoded { + out[i] = CHAR16(r) + } + return out +} + +// StringToCHAR16Z converts a Go string to a NUL-terminated UTF-16 code unit slice. +func StringToCHAR16Z(s string) []CHAR16 { + out := StringToCHAR16(s) + return append(out, 0) +} + +// BytesToCHAR16 converts UTF-8 text bytes to a UTF-16 code unit slice. +func BytesToCHAR16(b []byte) []CHAR16 { + return StringToCHAR16(string(b)) +} + +// BytesToCHAR16Z converts UTF-8 text bytes to a NUL-terminated UTF-16 code unit slice. +func BytesToCHAR16Z(b []byte) []CHAR16 { + return StringToCHAR16Z(string(b)) +} + +// CHAR16ToString converts a UTF-16 code unit slice to a Go string. +func CHAR16ToString(input []CHAR16) string { + if len(input) == 0 { + return "" + } + + units := make([]uint16, len(input)) + for i, c := range input { + units[i] = uint16(c) + } + return string(utf16.Decode(units)) +} + +// CHAR16ToBytes converts a UTF-16 code unit slice to UTF-8 text bytes. +func CHAR16ToBytes(input []CHAR16) []byte { + return []byte(CHAR16ToString(input)) +} + +// CHAR16PtrToString converts a NUL-terminated UTF-16 string pointer to a Go string. +func CHAR16PtrToString(input *CHAR16) string { + if input == nil { + return "" + } + + ptr := uintptr(unsafe.Pointer(input)) + length := 0 + for *(*CHAR16)(unsafe.Pointer(ptr)) != 0 { + length++ + ptr += 2 + } + return CHAR16PtrLenToString(input, length) +} + +// CHAR16PtrLenToString converts a UTF-16 string pointer with a known code unit count to a Go string. +func CHAR16PtrLenToString(input *CHAR16, length int) string { + if input == nil || length <= 0 { + return "" + } + return CHAR16ToString(unsafe.Slice(input, length)) +} diff --git a/src/device/uefi/clock.go b/src/device/uefi/clock.go new file mode 100644 index 0000000000..cae519eea0 --- /dev/null +++ b/src/device/uefi/clock.go @@ -0,0 +1,39 @@ +//go:build uefi + +package uefi + +import "sync" + +var calibrateMutex sync.Mutex +var calculatedFrequency uint64 + +func TicksFrequency() uint64 { + frequency := getTSCFrequency() + if frequency > 0 { + return frequency + } + + calibrateMutex.Lock() + defer calibrateMutex.Unlock() + if calculatedFrequency > 0 { + return calculatedFrequency + } + + var event EFI_EVENT + var index UINTN + if BS().CreateEvent(EVT_TIMER, TPL_CALLBACK, nil, nil, &event) != EFI_SUCCESS { + return 0 + } + defer BS().CloseEvent(event) + + start := Ticks() + if BS().SetTimer(event, TimerPeriodic, 250*10000) != EFI_SUCCESS { + return 0 + } + if BS().WaitForEvent(1, &event, &index) != EFI_SUCCESS { + return 0 + } + + calculatedFrequency = (Ticks() - start) * 4 + return calculatedFrequency +} diff --git a/src/device/uefi/efidef.go b/src/device/uefi/efidef.go new file mode 100644 index 0000000000..ef5f1c825b --- /dev/null +++ b/src/device/uefi/efidef.go @@ -0,0 +1,66 @@ +package uefi + +type UINTN uintptr +type EFI_STATUS UINTN +type EFI_TPL UINTN +type EFI_HANDLE uintptr +type EFI_EVENT uintptr +type EFI_PHYSICAL_ADDRESS uint64 + +type CHAR16 uint16 +type BOOLEAN bool +type VOID byte + +type EFI_GUID struct { + Data1 uint32 + Data2 uint16 + Data3 uint16 + Data4 [8]byte +} + +type EFI_TABLE_HEADER struct { + Signature uint64 + Revision uint32 + HeaderSize uint32 + CRC32 uint32 + Reserved uint32 +} + +type EFI_ALLOCATE_TYPE int + +const ( + AllocateAnyPages EFI_ALLOCATE_TYPE = iota + AllocateMaxAddress + AllocateAddress +) + +type EFI_MEMORY_TYPE int + +const ( + EfiReservedMemoryType EFI_MEMORY_TYPE = iota + EfiLoaderCode + EfiLoaderData + EfiBootServicesCode + EfiBootServicesData + EfiRuntimeServicesCode + EfiRuntimeServicesData + EfiConventionalMemory +) + +type EVENT_TYPE uint32 + +const ( + EVT_TIMER EVENT_TYPE = 0x80000000 +) + +const ( + TPL_CALLBACK EFI_TPL = 8 +) + +type EFI_TIMER_DELAY int + +const ( + TimerCancel EFI_TIMER_DELAY = iota + TimerPeriodic + TimerRelative +) diff --git a/src/device/uefi/efistatus.go b/src/device/uefi/efistatus.go new file mode 100644 index 0000000000..842cd66612 --- /dev/null +++ b/src/device/uefi/efistatus.go @@ -0,0 +1,111 @@ +package uefi + +const ( + uintnSize = 32 << (^uintptr(0) >> 63) + errorMask = 1 << uintptr(uintnSize-1) +) + +const ( + EFI_SUCCESS EFI_STATUS = 0 + EFI_LOAD_ERROR EFI_STATUS = errorMask | 1 + EFI_INVALID_PARAMETER EFI_STATUS = errorMask | 2 + EFI_UNSUPPORTED EFI_STATUS = errorMask | 3 + EFI_BAD_BUFFER_SIZE EFI_STATUS = errorMask | 4 + EFI_BUFFER_TOO_SMALL EFI_STATUS = errorMask | 5 + EFI_NOT_READY EFI_STATUS = errorMask | 6 + EFI_DEVICE_ERROR EFI_STATUS = errorMask | 7 + EFI_WRITE_PROTECTED EFI_STATUS = errorMask | 8 + EFI_OUT_OF_RESOURCES EFI_STATUS = errorMask | 9 + EFI_VOLUME_CORRUPTED EFI_STATUS = errorMask | 10 + EFI_VOLUME_FULL EFI_STATUS = errorMask | 11 + EFI_NO_MEDIA EFI_STATUS = errorMask | 12 + EFI_MEDIA_CHANGED EFI_STATUS = errorMask | 13 + EFI_NOT_FOUND EFI_STATUS = errorMask | 14 + EFI_ACCESS_DENIED EFI_STATUS = errorMask | 15 + EFI_NO_RESPONSE EFI_STATUS = errorMask | 16 + EFI_NO_MAPPING EFI_STATUS = errorMask | 17 + EFI_TIMEOUT EFI_STATUS = errorMask | 18 + EFI_NOT_STARTED EFI_STATUS = errorMask | 19 + EFI_ALREADY_STARTED EFI_STATUS = errorMask | 20 + EFI_ABORTED EFI_STATUS = errorMask | 21 + EFI_ICMP_ERROR EFI_STATUS = errorMask | 22 + EFI_TFTP_ERROR EFI_STATUS = errorMask | 23 + EFI_PROTOCOL_ERROR EFI_STATUS = errorMask | 24 + EFI_INCOMPATIBLE_VERSION EFI_STATUS = errorMask | 25 + EFI_SECURITY_VIOLATION EFI_STATUS = errorMask | 26 + EFI_CRC_ERROR EFI_STATUS = errorMask | 27 + EFI_END_OF_MEDIA EFI_STATUS = errorMask | 28 + EFI_END_OF_FILE EFI_STATUS = errorMask | 31 + EFI_INVALID_LANGUAGE EFI_STATUS = errorMask | 32 + EFI_COMPROMISED_DATA EFI_STATUS = errorMask | 33 + EFI_IP_ADDRESS_CONFLICT EFI_STATUS = errorMask | 34 + EFI_HTTP_ERROR EFI_STATUS = errorMask | 35 +) + +var errMap = map[EFI_STATUS]*Error{} + +var ( + ErrLoadError = newError(EFI_LOAD_ERROR, "image failed to load") + ErrInvalidParameter = newError(EFI_INVALID_PARAMETER, "a parameter was incorrect") + ErrUnsupported = newError(EFI_UNSUPPORTED, "operation not supported") + ErrBadBufferSize = newError(EFI_BAD_BUFFER_SIZE, "buffer size incorrect for request") + ErrBufferTooSmall = newError(EFI_BUFFER_TOO_SMALL, "buffer too small; size returned in parameter") + ErrNotReady = newError(EFI_NOT_READY, "no data pending") + ErrDeviceError = newError(EFI_DEVICE_ERROR, "physical device reported an error") + ErrWriteProtected = newError(EFI_WRITE_PROTECTED, "device is write-protected") + ErrOutOfResources = newError(EFI_OUT_OF_RESOURCES, "out of resources") + ErrVolumeCorrupted = newError(EFI_VOLUME_CORRUPTED, "filesystem inconsistency detected") + ErrVolumeFull = newError(EFI_VOLUME_FULL, "no more space on filesystem") + ErrNoMedia = newError(EFI_NO_MEDIA, "device contains no medium") + ErrMediaChanged = newError(EFI_MEDIA_CHANGED, "medium changed since last access") + ErrNotFound = newError(EFI_NOT_FOUND, "item not found") + ErrAccessDenied = newError(EFI_ACCESS_DENIED, "access denied") + ErrNoResponse = newError(EFI_NO_RESPONSE, "server not found or no response") + ErrNoMapping = newError(EFI_NO_MAPPING, "no device mapping exists") + ErrTimeout = newError(EFI_TIMEOUT, "timeout expired") + ErrNotStarted = newError(EFI_NOT_STARTED, "protocol not started") + ErrAlreadyStarted = newError(EFI_ALREADY_STARTED, "protocol already started") + ErrAborted = newError(EFI_ABORTED, "operation aborted") + ErrICMPError = newError(EFI_ICMP_ERROR, "ICMP error during network operation") + ErrTFTPError = newError(EFI_TFTP_ERROR, "TFTP error during network operation") + ErrProtocolError = newError(EFI_PROTOCOL_ERROR, "protocol error during network operation") + ErrIncompatibleVersion = newError(EFI_INCOMPATIBLE_VERSION, "requested version incompatible") + ErrSecurityViolation = newError(EFI_SECURITY_VIOLATION, "security violation") + ErrCRCError = newError(EFI_CRC_ERROR, "CRC error detected") + ErrEndOfMedia = newError(EFI_END_OF_MEDIA, "beginning or end of media reached") + ErrEndOfFile = newError(EFI_END_OF_FILE, "end of file reached") + ErrInvalidLanguage = newError(EFI_INVALID_LANGUAGE, "invalid language specified") + ErrCompromisedData = newError(EFI_COMPROMISED_DATA, "data security status unknown or compromised") + ErrIPAddressConflict = newError(EFI_IP_ADDRESS_CONFLICT, "IP address conflict detected") + ErrHTTPError = newError(EFI_HTTP_ERROR, "HTTP error during network operation") +) + +type Error struct { + code EFI_STATUS + msg string +} + +func newError(code EFI_STATUS, msg string) *Error { + err := &Error{code: code, msg: msg} + errMap[code] = err + return err +} + +func (e *Error) Error() string { + return e.msg +} + +func (e *Error) Status() EFI_STATUS { + return e.code +} + +func StatusError(status EFI_STATUS) *Error { + if status == EFI_SUCCESS { + return nil + } + err, ok := errMap[status] + if !ok { + return newError(status, "unknown EFI error") + } + return err +} diff --git a/src/device/uefi/simple_text_out.go b/src/device/uefi/simple_text_out.go new file mode 100644 index 0000000000..72e664475d --- /dev/null +++ b/src/device/uefi/simple_text_out.go @@ -0,0 +1,74 @@ +package uefi + +import "unsafe" + +func booleanArg(v BOOLEAN) uintptr { + if v { + return 1 + } + return 0 +} + +type EFI_SIMPLE_TEXT_OUTPUT_MODE struct { + MaxMode int32 + Mode int32 + Attribute int32 + CursorColumn int32 + CursorRow int32 + CursorVisible BOOLEAN +} + +type EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL struct { + reset uintptr + outputString uintptr + testString uintptr + queryMode uintptr + setMode uintptr + setAttribute uintptr + clearScreen uintptr + setCursorPosition uintptr + enableCursor uintptr + Mode *EFI_SIMPLE_TEXT_OUTPUT_MODE +} + +func (p *EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL) Reset(extendedVerification BOOLEAN) EFI_STATUS { + return UefiCall2(p.reset, uintptr(unsafe.Pointer(p)), booleanArg(extendedVerification)) +} + +func (p *EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL) OutputString(s *CHAR16) EFI_STATUS { + return UefiCall2(p.outputString, uintptr(unsafe.Pointer(p)), uintptr(unsafe.Pointer(s))) +} + +func (p *EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL) TestString(s *CHAR16) EFI_STATUS { + return UefiCall2(p.testString, uintptr(unsafe.Pointer(p)), uintptr(unsafe.Pointer(s))) +} + +func (p *EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL) QueryMode(modeNumber UINTN, columns *UINTN, rows *UINTN) EFI_STATUS { + return UefiCall4( + p.queryMode, + uintptr(unsafe.Pointer(p)), + uintptr(modeNumber), + uintptr(unsafe.Pointer(columns)), + uintptr(unsafe.Pointer(rows)), + ) +} + +func (p *EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL) SetMode(modeNumber UINTN) EFI_STATUS { + return UefiCall2(p.setMode, uintptr(unsafe.Pointer(p)), uintptr(modeNumber)) +} + +func (p *EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL) SetAttribute(attribute UINTN) EFI_STATUS { + return UefiCall2(p.setAttribute, uintptr(unsafe.Pointer(p)), uintptr(attribute)) +} + +func (p *EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL) ClearScreen() EFI_STATUS { + return UefiCall1(p.clearScreen, uintptr(unsafe.Pointer(p))) +} + +func (p *EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL) SetCursorPosition(column UINTN, row UINTN) EFI_STATUS { + return UefiCall3(p.setCursorPosition, uintptr(unsafe.Pointer(p)), uintptr(column), uintptr(row)) +} + +func (p *EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL) EnableCursor(visible BOOLEAN) EFI_STATUS { + return UefiCall2(p.enableCursor, uintptr(unsafe.Pointer(p)), booleanArg(visible)) +} diff --git a/src/device/uefi/tables.go b/src/device/uefi/tables.go new file mode 100644 index 0000000000..230cbc8005 --- /dev/null +++ b/src/device/uefi/tables.go @@ -0,0 +1,123 @@ +package uefi + +import "unsafe" + +type EFI_RUNTIME_SERVICES struct { + Hdr EFI_TABLE_HEADER + getTime uintptr + setTime uintptr + getWakeupTime uintptr + setWakeupTime uintptr + setVirtualAddressMap uintptr + convertPointer uintptr + getVariable uintptr + getNextVariableName uintptr + setVariable uintptr + getNextHighMonoCount uintptr + resetSystem uintptr + updateCapsule uintptr + queryCapsuleCaps uintptr + queryVariableInfo uintptr +} + +type EFI_BOOT_SERVICES struct { + Hdr EFI_TABLE_HEADER + raiseTPL uintptr + restoreTPL uintptr + allocatePages uintptr + freePages uintptr + getMemoryMap uintptr + allocatePool uintptr + freePool uintptr + createEvent uintptr + setTimer uintptr + waitForEvent uintptr + signalEvent uintptr + closeEvent uintptr + checkEvent uintptr + installProtocolInterface uintptr + reinstallProtocolIFace uintptr + uninstallProtocolIFace uintptr + handleProtocol uintptr + reserved *VOID + registerProtocolNotify uintptr + locateHandle uintptr + locateDevicePath uintptr + installConfigurationTable uintptr + loadImage uintptr + startImage uintptr + exit uintptr + unloadImage uintptr + exitBootServices uintptr + getNextMonotonicCount uintptr + stall uintptr + setWatchdogTimer uintptr + connectController uintptr + disconnectController uintptr + openProtocol uintptr + closeProtocol uintptr + openProtocolInformation uintptr + protocolsPerHandle uintptr + locateHandleBuffer uintptr + locateProtocol uintptr +} + +func (p *EFI_BOOT_SERVICES) AllocatePages(typ EFI_ALLOCATE_TYPE, memoryType EFI_MEMORY_TYPE, pages UINTN, memory *EFI_PHYSICAL_ADDRESS) EFI_STATUS { + return UefiCall4(p.allocatePages, uintptr(typ), uintptr(memoryType), uintptr(pages), uintptr(unsafe.Pointer(memory))) +} + +func (p *EFI_BOOT_SERVICES) FreePages(memory EFI_PHYSICAL_ADDRESS, pages UINTN) EFI_STATUS { + return UefiCall2(p.freePages, uintptr(memory), uintptr(pages)) +} + +func (p *EFI_BOOT_SERVICES) CreateEvent(typ EVENT_TYPE, notifyTPL EFI_TPL, notifyFunction unsafe.Pointer, notifyContext unsafe.Pointer, event *EFI_EVENT) EFI_STATUS { + return UefiCall5(p.createEvent, uintptr(typ), uintptr(notifyTPL), uintptr(notifyFunction), uintptr(notifyContext), uintptr(unsafe.Pointer(event))) +} + +func (p *EFI_BOOT_SERVICES) SetTimer(event EFI_EVENT, typ EFI_TIMER_DELAY, triggerTime uint64) EFI_STATUS { + return UefiCall3(p.setTimer, uintptr(event), uintptr(typ), uintptr(triggerTime)) +} + +func (p *EFI_BOOT_SERVICES) WaitForEvent(numberOfEvents UINTN, event *EFI_EVENT, index *UINTN) EFI_STATUS { + return UefiCall3(p.waitForEvent, uintptr(numberOfEvents), uintptr(unsafe.Pointer(event)), uintptr(unsafe.Pointer(index))) +} + +func (p *EFI_BOOT_SERVICES) CloseEvent(event EFI_EVENT) EFI_STATUS { + return UefiCall1(p.closeEvent, uintptr(event)) +} + +func (p *EFI_BOOT_SERVICES) CheckEvent(event EFI_EVENT) EFI_STATUS { + return UefiCall1(p.checkEvent, uintptr(event)) +} + +func (p *EFI_BOOT_SERVICES) HandleProtocol(handle EFI_HANDLE, protocol *EFI_GUID, iface unsafe.Pointer) EFI_STATUS { + return UefiCall3(p.handleProtocol, uintptr(handle), uintptr(unsafe.Pointer(protocol)), uintptr(iface)) +} + +func (p *EFI_BOOT_SERVICES) LocateProtocol(protocol *EFI_GUID, registration *VOID, iface unsafe.Pointer) EFI_STATUS { + return UefiCall3(p.locateProtocol, uintptr(unsafe.Pointer(protocol)), uintptr(unsafe.Pointer(registration)), uintptr(iface)) +} + +func (p *EFI_BOOT_SERVICES) Exit(imageHandle EFI_HANDLE, exitStatus EFI_STATUS, exitDataSize UINTN, exitData *CHAR16) EFI_STATUS { + return UefiCall4(p.exit, uintptr(imageHandle), uintptr(exitStatus), uintptr(exitDataSize), uintptr(unsafe.Pointer(exitData))) +} + +func (p *EFI_BOOT_SERVICES) SetWatchdogTimer(timeout UINTN, watchdogCode uint64, dataSize UINTN, watchdogData *CHAR16) EFI_STATUS { + return UefiCall4(p.setWatchdogTimer, uintptr(timeout), uintptr(watchdogCode), uintptr(dataSize), uintptr(unsafe.Pointer(watchdogData))) +} + +type EFI_SYSTEM_TABLE struct { + Hdr EFI_TABLE_HEADER + FirmwareVendor *CHAR16 + FirmwareRevision uint32 + ConsoleInHandle EFI_HANDLE + ConIn *VOID + ConsoleOutHandle EFI_HANDLE + ConOut *EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL + StandardErrorHandle EFI_HANDLE + StdErr *EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL + RuntimeServices *EFI_RUNTIME_SERVICES + BootServices *EFI_BOOT_SERVICES + NumberOfTableEntries UINTN + ConfigurationTable *VOID +} diff --git a/src/device/uefi/text_output.go b/src/device/uefi/text_output.go new file mode 100644 index 0000000000..7bc4ffeebb --- /dev/null +++ b/src/device/uefi/text_output.go @@ -0,0 +1,41 @@ +package uefi + +import "errors" + +var errNilTextOutputProtocol = errors.New("uefi: nil simple text output protocol") + +type TextOutput struct { + proto *EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL +} + +func NewTextOutput(proto *EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL) *TextOutput { + return &TextOutput{proto: proto} +} + +func ConsoleOut() *TextOutput { + return NewTextOutput(ST().ConOut) +} + +func StandardError() *TextOutput { + return NewTextOutput(ST().StdErr) +} + +func (w *TextOutput) Write(p []byte) (int, error) { + if w == nil || w.proto == nil { + return 0, errNilTextOutputProtocol + } + if len(p) == 0 { + return 0, nil + } + + buf := StringToCHAR16Z(string(p)) + status := w.proto.OutputString(&buf[0]) + if status != EFI_SUCCESS { + return 0, StatusError(status) + } + return len(p), nil +} + +func (w *TextOutput) WriteString(s string) (int, error) { + return w.Write([]byte(s)) +} diff --git a/src/device/uefi/util.go b/src/device/uefi/util.go new file mode 100644 index 0000000000..a37672a8ec --- /dev/null +++ b/src/device/uefi/util.go @@ -0,0 +1,29 @@ +//go:build uefi + +package uefi + +import "unsafe" + +var systemTable *EFI_SYSTEM_TABLE +var imageHandle uintptr + +//go:nobounds +func Init(argImageHandle uintptr, argSystemTable uintptr) { + systemTable = (*EFI_SYSTEM_TABLE)(unsafe.Pointer(argSystemTable)) + imageHandle = argImageHandle +} + +func ST() *EFI_SYSTEM_TABLE { + return systemTable +} + +func BS() *EFI_BOOT_SERVICES { + if systemTable == nil { + return nil + } + return systemTable.BootServices +} + +func GetImageHandle() EFI_HANDLE { + return EFI_HANDLE(imageHandle) +} diff --git a/src/machine/machine_uefi.go b/src/machine/machine_uefi.go new file mode 100644 index 0000000000..94659d2b3e --- /dev/null +++ b/src/machine/machine_uefi.go @@ -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 +} diff --git a/src/runtime/baremetal_memory.go b/src/runtime/baremetal_memory.go index 02f870ad69..52e30b5312 100644 --- a/src/runtime/baremetal_memory.go +++ b/src/runtime/baremetal_memory.go @@ -1,4 +1,4 @@ -//go:build baremetal +//go:build baremetal && !uefi package runtime diff --git a/src/runtime/gc_globals.go b/src/runtime/gc_globals.go index 3e8f857618..58e70ca3e1 100644 --- a/src/runtime/gc_globals.go +++ b/src/runtime/gc_globals.go @@ -1,4 +1,4 @@ -//go:build baremetal || tinygo.wasm +//go:build (baremetal || tinygo.wasm) && !uefi package runtime diff --git a/src/runtime/gc_leaking_uefi.go b/src/runtime/gc_leaking_uefi.go new file mode 100644 index 0000000000..4985221b35 --- /dev/null +++ b/src/runtime/gc_leaking_uefi.go @@ -0,0 +1,9 @@ +//go:build gc.leaking && uefi + +package runtime + +import _ "unsafe" + +//go:export tinygo_scanstack +func tinygo_scanstack() { +} diff --git a/src/runtime/interrupt/interrupt_none.go b/src/runtime/interrupt/interrupt_none.go index ea8bdb68c6..22f1644f0f 100644 --- a/src/runtime/interrupt/interrupt_none.go +++ b/src/runtime/interrupt/interrupt_none.go @@ -1,4 +1,4 @@ -//go:build !baremetal || tkey +//go:build !baremetal || tkey || uefi package interrupt diff --git a/src/runtime/os_windows_pe.go b/src/runtime/os_windows_pe.go index febe0b2105..53bc62f2ba 100644 --- a/src/runtime/os_windows_pe.go +++ b/src/runtime/os_windows_pe.go @@ -1,4 +1,4 @@ -//go:build windows +//go:build windows || uefi package runtime diff --git a/src/runtime/runtime_uefi.go b/src/runtime/runtime_uefi.go new file mode 100644 index 0000000000..9849ec9d92 --- /dev/null +++ b/src/runtime/runtime_uefi.go @@ -0,0 +1,132 @@ +//go:build uefi + +package runtime + +import "device/uefi" + +//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) { + 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 +} diff --git a/src/runtime/runtime_uefi_schednone.go b/src/runtime/runtime_uefi_schednone.go new file mode 100644 index 0000000000..a0fb9523d5 --- /dev/null +++ b/src/runtime/runtime_uefi_schednone.go @@ -0,0 +1,32 @@ +//go:build uefi && scheduler.none + +package runtime + +//go:noinline +func runProgram() { + runProgramInitRand() + runProgramInitHeap() + runProgramInitAll() + runProgramCallMain() + mainExited = true +} + +//go:noinline +func runProgramInitRand() { + initRand() +} + +//go:noinline +func runProgramInitHeap() { + initHeap() +} + +//go:noinline +func runProgramInitAll() { + initAll() +} + +//go:noinline +func runProgramCallMain() { + callMain() +} diff --git a/src/runtime/scheduler_cooperative.go b/src/runtime/scheduler_cooperative.go index 2c2bf09926..18674f8099 100644 --- a/src/runtime/scheduler_cooperative.go +++ b/src/runtime/scheduler_cooperative.go @@ -235,6 +235,9 @@ func sleep(duration int64) { if duration <= 0 { return } + if schedulerSleepCustom(duration) { + return + } addSleepTask(task.Current(), nanosecondsToTicks(duration)) task.Pause() diff --git a/src/runtime/sleep_custom_default.go b/src/runtime/sleep_custom_default.go new file mode 100644 index 0000000000..a32fe1dce3 --- /dev/null +++ b/src/runtime/sleep_custom_default.go @@ -0,0 +1,7 @@ +//go:build !(scheduler.tasks && uefi) + +package runtime + +func schedulerSleepCustom(duration int64) bool { + return false +} 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/src/runtime/wait_other.go b/src/runtime/wait_other.go index ebac63d743..037cb37e71 100644 --- a/src/runtime/wait_other.go +++ b/src/runtime/wait_other.go @@ -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 diff --git a/targets/uefi-amd64.json b/targets/uefi-amd64.json new file mode 100644 index 0000000000..c594aecce6 --- /dev/null +++ b/targets/uefi-amd64.json @@ -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"] +}