nintendoswitch: Add experimental Nintendo Switch support without CRT
Bare minimal nintendo switch support using LLD
Этот коммит содержится в:
родитель
d4e04e4e49
коммит
3650c2c739
19 изменённых файлов: 401 добавлений и 4 удалений
2
Makefile
2
Makefile
|
@ -342,6 +342,8 @@ endif
|
|||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=pca10040 -opt=1 examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -o test.elf -target=nintendoswitch examples/serial
|
||||
@$(MD5SUM) test.elf
|
||||
|
||||
wasmtest:
|
||||
$(GO) test ./tests/wasm
|
||||
|
|
|
@ -284,6 +284,16 @@ func (c *Config) CodeModel() string {
|
|||
return "default"
|
||||
}
|
||||
|
||||
// RelocationModel returns the relocation model in use on this platform. Valid
|
||||
// values are "static", "pic", "dynamicnopic".
|
||||
func (c *Config) RelocationModel() string {
|
||||
if c.Target.RelocationModel != "" {
|
||||
return c.Target.RelocationModel
|
||||
}
|
||||
|
||||
return "static"
|
||||
}
|
||||
|
||||
type TestConfig struct {
|
||||
CompileTestBinary bool
|
||||
// TODO: Filter the test functions to run, include verbose flag, etc
|
||||
|
|
|
@ -50,6 +50,7 @@ type TargetSpec struct {
|
|||
OpenOCDTransport string `json:"openocd-transport"`
|
||||
JLinkDevice string `json:"jlink-device"`
|
||||
CodeModel string `json:"code-model"`
|
||||
RelocationModel string `json:"relocation-model"`
|
||||
}
|
||||
|
||||
// copyProperties copies all properties that are set in spec2 into itself.
|
||||
|
@ -134,6 +135,10 @@ func (spec *TargetSpec) copyProperties(spec2 *TargetSpec) {
|
|||
if spec2.CodeModel != "" {
|
||||
spec.CodeModel = spec2.CodeModel
|
||||
}
|
||||
|
||||
if spec2.RelocationModel != "" {
|
||||
spec.RelocationModel = spec2.RelocationModel
|
||||
}
|
||||
}
|
||||
|
||||
// load reads a target specification from the JSON in the given io.Reader. It
|
||||
|
|
|
@ -93,6 +93,7 @@ func NewTargetMachine(config *compileopts.Config) (llvm.TargetMachine, error) {
|
|||
features := strings.Join(config.Features(), ",")
|
||||
|
||||
var codeModel llvm.CodeModel
|
||||
var relocationModel llvm.RelocMode
|
||||
|
||||
switch config.CodeModel() {
|
||||
case "default":
|
||||
|
@ -109,7 +110,16 @@ func NewTargetMachine(config *compileopts.Config) (llvm.TargetMachine, error) {
|
|||
codeModel = llvm.CodeModelLarge
|
||||
}
|
||||
|
||||
machine := target.CreateTargetMachine(config.Triple(), config.CPU(), features, llvm.CodeGenLevelDefault, llvm.RelocStatic, codeModel)
|
||||
switch config.RelocationModel() {
|
||||
case "static":
|
||||
relocationModel = llvm.RelocStatic
|
||||
case "pic":
|
||||
relocationModel = llvm.RelocPIC
|
||||
case "dynamicnopic":
|
||||
relocationModel = llvm.RelocDynamicNoPic
|
||||
}
|
||||
|
||||
machine := target.CreateTargetMachine(config.Triple(), config.CPU(), features, llvm.CodeGenLevelDefault, relocationModel, codeModel)
|
||||
return machine, nil
|
||||
}
|
||||
|
||||
|
|
|
@ -165,7 +165,7 @@ func mergeDirectory(goroot, tinygoroot, tmpgoroot, importPath string, overrides
|
|||
// with the TinyGo version. This is the case on some targets.
|
||||
func needsSyscallPackage(buildTags []string) bool {
|
||||
for _, tag := range buildTags {
|
||||
if tag == "baremetal" || tag == "darwin" {
|
||||
if tag == "baremetal" || tag == "darwin" || tag == "nintendoswitch" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
|
73
src/runtime/runtime_nintendoswitch.go
Обычный файл
73
src/runtime/runtime_nintendoswitch.go
Обычный файл
|
@ -0,0 +1,73 @@
|
|||
// +build nintendoswitch
|
||||
|
||||
package runtime
|
||||
|
||||
type timeUnit int64
|
||||
|
||||
const asyncScheduler = false
|
||||
|
||||
func postinit() {}
|
||||
|
||||
// Entry point for Go. Initialize all packages and call main.main().
|
||||
//export main
|
||||
func main() int {
|
||||
preinit()
|
||||
run()
|
||||
|
||||
// Call exit to correctly finish the program
|
||||
// Without this, the application crashes at start, not sure why
|
||||
return exit(0)
|
||||
}
|
||||
|
||||
// sleepTicks sleeps for the specified system ticks
|
||||
func sleepTicks(d timeUnit) {
|
||||
sleepThread(uint64(ticksToNanoseconds(d)))
|
||||
}
|
||||
|
||||
// armTicksToNs converts cpu ticks to nanoseconds
|
||||
// Nintendo Switch CPU ticks has a fixed rate at 19200000
|
||||
// It is basically 52 ns per tick
|
||||
// The formula 625 / 12 is equivalent to 1e9 / 19200000
|
||||
func ticksToNanoseconds(tick timeUnit) int64 {
|
||||
return int64(tick * 625 / 12)
|
||||
}
|
||||
|
||||
func nanosecondsToTicks(ns int64) timeUnit {
|
||||
return timeUnit(12 * ns / 625)
|
||||
}
|
||||
|
||||
func ticks() timeUnit {
|
||||
return timeUnit(ticksToNanoseconds(timeUnit(getArmSystemTick())))
|
||||
}
|
||||
|
||||
var stdoutBuffer = make([]byte, 0, 120)
|
||||
|
||||
func putchar(c byte) {
|
||||
if c == '\n' || len(stdoutBuffer)+1 >= 120 {
|
||||
NxOutputString(string(stdoutBuffer))
|
||||
stdoutBuffer = stdoutBuffer[:0]
|
||||
return
|
||||
}
|
||||
|
||||
stdoutBuffer = append(stdoutBuffer, c)
|
||||
}
|
||||
|
||||
func usleep(usec uint) int {
|
||||
sleepThread(uint64(usec) * 1000)
|
||||
return 0
|
||||
}
|
||||
|
||||
func abort() {
|
||||
for {
|
||||
exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
//export sleepThread
|
||||
func sleepThread(nanos uint64)
|
||||
|
||||
//export exit
|
||||
func exit(code int) int
|
||||
|
||||
//export armGetSystemTick
|
||||
func getArmSystemTick() int64
|
45
src/runtime/runtime_nintendoswitch.s
Обычный файл
45
src/runtime/runtime_nintendoswitch.s
Обычный файл
|
@ -0,0 +1,45 @@
|
|||
.section .text.armGetSystemTick, "ax", %progbits
|
||||
.global armGetSystemTick
|
||||
.type armGetSystemTick, %function
|
||||
.align 2
|
||||
armGetSystemTick:
|
||||
mrs x0, cntpct_el0
|
||||
ret
|
||||
|
||||
.section .text.nxOutputString, "ax", %progbits
|
||||
.global nxOutputString
|
||||
.type nxOutputString, %function
|
||||
.align 2
|
||||
.cfi_startproc
|
||||
nxOutputString:
|
||||
svc 0x27
|
||||
ret
|
||||
.cfi_endproc
|
||||
|
||||
.section .text.exit, "ax", %progbits
|
||||
.global exit
|
||||
.type exit, %function
|
||||
.align 2
|
||||
exit:
|
||||
svc 0x7
|
||||
ret
|
||||
|
||||
.section .text.setHeapSize, "ax", %progbits
|
||||
.global setHeapSize
|
||||
.type setHeapSize, %function
|
||||
.align 2
|
||||
setHeapSize:
|
||||
str x0, [sp, #-16]!
|
||||
svc 0x1
|
||||
ldr x2, [sp], #16
|
||||
str x1, [x2]
|
||||
ret
|
||||
|
||||
|
||||
.section .text.sleepThread, "ax", %progbits
|
||||
.global sleepThread
|
||||
.type sleepThread, %function
|
||||
.align 2
|
||||
sleepThread:
|
||||
svc 0xB
|
||||
ret
|
31
src/runtime/runtime_nintendoswitch_heap.go
Обычный файл
31
src/runtime/runtime_nintendoswitch_heap.go
Обычный файл
|
@ -0,0 +1,31 @@
|
|||
// +build nintendoswitch
|
||||
|
||||
// +build gc.conservative gc.leaking
|
||||
|
||||
package runtime
|
||||
|
||||
import "unsafe"
|
||||
|
||||
const heapSize = 0x2000000 * 16 // Default by libnx
|
||||
|
||||
//go:extern _stack_top
|
||||
var stackTopSymbol [0]byte
|
||||
|
||||
var (
|
||||
heapStart = uintptr(0)
|
||||
heapEnd = uintptr(0)
|
||||
stackTop = uintptr(unsafe.Pointer(&stackTopSymbol))
|
||||
)
|
||||
|
||||
//export setHeapSize
|
||||
func setHeapSize(addr *uintptr, length uint64) uint64
|
||||
|
||||
func preinit() {
|
||||
setHeapSize(&heapStart, heapSize)
|
||||
|
||||
if heapStart == 0 {
|
||||
panic("failed to allocate heap")
|
||||
}
|
||||
|
||||
heapEnd = heapStart + heapSize
|
||||
}
|
7
src/runtime/runtime_nintendoswitch_noheap.go
Обычный файл
7
src/runtime/runtime_nintendoswitch_noheap.go
Обычный файл
|
@ -0,0 +1,7 @@
|
|||
// +build nintendoswitch
|
||||
|
||||
// +build gc.none gc.extalloc
|
||||
|
||||
package runtime
|
||||
|
||||
func preinit() {}
|
22
src/runtime/runtime_nintendoswitch_svc.go
Обычный файл
22
src/runtime/runtime_nintendoswitch_svc.go
Обычный файл
|
@ -0,0 +1,22 @@
|
|||
// +build nintendoswitch
|
||||
|
||||
package runtime
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// Result nxOutputString(const char *str, u64 size)
|
||||
//export nxOutputString
|
||||
func nxOutputString(str *uint8, size uint64) uint64
|
||||
|
||||
func NxOutputString(str string) uint64 {
|
||||
strData := (*_string)(unsafe.Pointer(&str))
|
||||
return nxOutputString((*uint8)(unsafe.Pointer(strData.ptr)), uint64(strData.length))
|
||||
}
|
||||
|
||||
//export malloc
|
||||
func extalloc(size uintptr) unsafe.Pointer
|
||||
|
||||
//export free
|
||||
func extfree(ptr unsafe.Pointer)
|
|
@ -1,4 +1,5 @@
|
|||
// +build darwin linux,!baremetal freebsd,!baremetal
|
||||
// +build !nintendoswitch
|
||||
|
||||
package runtime
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
// +build darwin linux,!baremetal freebsd,!baremetal
|
||||
// +build !nintendoswitch
|
||||
|
||||
// +build gc.conservative gc.leaking
|
||||
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
// +build darwin linux,!baremetal freebsd,!baremetal
|
||||
|
||||
// +build !nintendoswitch
|
||||
|
||||
// +build gc.none gc.extalloc
|
||||
|
||||
package runtime
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// +build darwin
|
||||
// +build darwin nintendoswitch
|
||||
|
||||
package syscall
|
||||
|
||||
|
|
48
src/syscall/syscall_nintendoswitch.go
Обычный файл
48
src/syscall/syscall_nintendoswitch.go
Обычный файл
|
@ -0,0 +1,48 @@
|
|||
// +build nintendoswitch
|
||||
|
||||
package syscall
|
||||
|
||||
import "errors"
|
||||
|
||||
// A Signal is a number describing a process signal.
|
||||
// It implements the os.Signal interface.
|
||||
type Signal int
|
||||
|
||||
const (
|
||||
_ Signal = iota
|
||||
SIGCHLD
|
||||
SIGINT
|
||||
SIGKILL
|
||||
SIGTRAP
|
||||
SIGQUIT
|
||||
SIGTERM
|
||||
)
|
||||
|
||||
// File system
|
||||
|
||||
const (
|
||||
Stdin = 0
|
||||
Stdout = 1
|
||||
Stderr = 2
|
||||
)
|
||||
|
||||
const (
|
||||
O_RDONLY = 0
|
||||
O_WRONLY = 1
|
||||
O_RDWR = 2
|
||||
|
||||
O_CREAT = 0100
|
||||
O_CREATE = O_CREAT
|
||||
O_TRUNC = 01000
|
||||
O_APPEND = 02000
|
||||
O_EXCL = 0200
|
||||
O_SYNC = 010000
|
||||
|
||||
O_CLOEXEC = 0
|
||||
)
|
||||
|
||||
var dummyError = errors.New("unknown syscall error")
|
||||
|
||||
func getErrno() error {
|
||||
return dummyError
|
||||
}
|
|
@ -2,7 +2,7 @@
|
|||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build baremetal
|
||||
// +build baremetal nintendoswitch
|
||||
|
||||
package syscall
|
||||
|
||||
|
|
28
targets/nintendoswitch.json
Обычный файл
28
targets/nintendoswitch.json
Обычный файл
|
@ -0,0 +1,28 @@
|
|||
{
|
||||
"llvm-target": "aarch64",
|
||||
"build-tags": ["nintendoswitch", "arm64"],
|
||||
"goos": "linux",
|
||||
"goarch": "arm64",
|
||||
"compiler": "clang",
|
||||
"linker": "ld.lld",
|
||||
"rtlib": "compiler-rt",
|
||||
"libc": "picolibc",
|
||||
"gc": "conservative",
|
||||
"relocation-model": "pic",
|
||||
"cflags": [
|
||||
"-target", "aarch64-none-linux-gnu",
|
||||
"-mtune=cortex-a57",
|
||||
"-fPIE",
|
||||
"-Werror",
|
||||
"-Qunused-arguments",
|
||||
"-fshort-enums",
|
||||
"-fomit-frame-pointer",
|
||||
"-fno-exceptions", "-fno-unwind-tables",
|
||||
"-ffunction-sections", "-fdata-sections"
|
||||
],
|
||||
"linkerscript": "targets/nintendoswitch.ld",
|
||||
"extra-files": [
|
||||
"targets/nintendoswitch.s",
|
||||
"src/runtime/runtime_nintendoswitch.s"
|
||||
]
|
||||
}
|
61
targets/nintendoswitch.ld
Обычный файл
61
targets/nintendoswitch.ld
Обычный файл
|
@ -0,0 +1,61 @@
|
|||
OUTPUT_FORMAT(elf64-littleaarch64)
|
||||
OUTPUT_ARCH(aarch64)
|
||||
ENTRY(_start)
|
||||
|
||||
PHDRS
|
||||
{
|
||||
text PT_LOAD FLAGS(5);
|
||||
rodata PT_LOAD FLAGS(4);
|
||||
data PT_LOAD FLAGS(6);
|
||||
bss PT_LOAD FLAGS(6);
|
||||
dynamic PT_DYNAMIC;
|
||||
}
|
||||
|
||||
SECTIONS
|
||||
{
|
||||
. = 0;
|
||||
|
||||
.text : ALIGN(0x1000) {
|
||||
HIDDEN(__text_start = .);
|
||||
KEEP(*(.text.jmp))
|
||||
|
||||
. = 0x80;
|
||||
|
||||
*(.text .text.*)
|
||||
}
|
||||
|
||||
/* Read-only sections */
|
||||
|
||||
. = ALIGN(0x1000);
|
||||
|
||||
.rodata : { *(.rodata .rodata.*) } :rodata
|
||||
.mod0 : {
|
||||
KEEP(crt0.nso.o(.data.mod0))
|
||||
KEEP(crt0.nro.o(.data.mod0))
|
||||
KEEP(crt0.lib.nro.o(.data.mod0))
|
||||
}
|
||||
|
||||
/* Read-write sections */
|
||||
. = ALIGN(0x1000);
|
||||
|
||||
.data : {
|
||||
*(.data .data.*)
|
||||
} :data
|
||||
|
||||
.dynamic : {
|
||||
HIDDEN(__dynamic_start = .);
|
||||
*(.dynamic)
|
||||
}
|
||||
|
||||
/* BSS section */
|
||||
|
||||
. = ALIGN(0x1000);
|
||||
|
||||
.bss : {
|
||||
HIDDEN(__bss_start = .);
|
||||
*(.bss .bss.*)
|
||||
*(COMMON)
|
||||
. = ALIGN(8);
|
||||
HIDDEN(__bss_end = .);
|
||||
} :bss
|
||||
}
|
51
targets/nintendoswitch.s
Обычный файл
51
targets/nintendoswitch.s
Обычный файл
|
@ -0,0 +1,51 @@
|
|||
.section .text.jmp, "x"
|
||||
.global _start
|
||||
_start:
|
||||
b start
|
||||
.word _mod_header - _start
|
||||
|
||||
.section .data.mod0
|
||||
.word 0, 8
|
||||
|
||||
.global _mod_header
|
||||
_mod_header:
|
||||
.ascii "MOD0"
|
||||
.word __dynamic_start - _mod_header
|
||||
.word __bss_start - _mod_header
|
||||
.word __bss_end - _mod_header
|
||||
.word 0, 0 // eh_frame_hdr start/end
|
||||
.word 0 // runtime-generated module object offset
|
||||
|
||||
.section .text, "x"
|
||||
.global start
|
||||
start:
|
||||
|
||||
// save lr
|
||||
mov x7, x30
|
||||
|
||||
// get aslr base
|
||||
bl +4
|
||||
sub x6, x30, #0x88
|
||||
|
||||
// context ptr and main thread handle
|
||||
mov x5, x0
|
||||
mov x4, x1
|
||||
|
||||
// clear .bss
|
||||
adrp x5, __bss_start
|
||||
add x5, x5, #:lo12:__bss_start
|
||||
adrp x6, __bss_end
|
||||
add x6, x6, #:lo12:__bss_end
|
||||
|
||||
bssloop:
|
||||
cmp x5, x6
|
||||
b.eq run
|
||||
str xzr, [x5]
|
||||
add x5, x5, 8
|
||||
b bssloop
|
||||
|
||||
run:
|
||||
// call entrypoint
|
||||
adrp x30, exit
|
||||
add x30, x30, #:lo12:exit
|
||||
b main
|
Загрузка…
Создание таблицы
Сослаться в новой задаче