aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/runtime/runtime_nintendoswitch.go
blob: 2d3677bf0522ee8808713ef72327e7880498d193 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
//go:build nintendoswitch

package runtime

import "unsafe"

type timeUnit int64

const (
	// Handles
	infoTypeTotalMemorySize = 6          // Total amount of memory available for process.
	infoTypeUsedMemorySize  = 7          // Amount of memory currently used by process.
	currentProcessHandle    = 0xFFFF8001 // Pseudo handle for the current process.

	// Types of config Entry
	envEntryTypeEndOfList        = 0 // Entry list terminator.
	envEntryTypeMainThreadHandle = 1 // Provides the handle to the main thread.
	envEntryTypeOverrideHeap     = 3 // Provides heap override information.

	// Default heap size allocated by libnx
	defaultHeapSize = 0x2000000 * 16

	debugInit = false
)

//go:extern _saved_return_address
var savedReturnAddress uintptr

//export __stack_top
var stackTop uintptr

//go:extern _context
var context uintptr

//go:extern _main_thread
var mainThread uintptr

var (
	heapStart = uintptr(0)
	heapEnd   = uintptr(0)
	usedRam   = uint64(0)
	totalRam  = uint64(0)
	totalHeap = uint64(0)
)

func preinit() {
	// Unsafe to use heap here
	setupEnv()
	setupHeap()
}

// Entry point for Go. Initialize all packages and call main.main().
//
//export main
func main() {
	preinit()
	run()

	// Call exit to correctly finish the program
	// Without this, the application crashes at start, not sure why
	for {
		exit(0)
	}
}

// sleepTicks sleeps for the specified system ticks
func sleepTicks(d timeUnit) {
	svcSleepThread(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())))
}

// timeOffset is how long the monotonic clock started after the Unix epoch. It
// should be a positive integer under normal operation or zero when it has not
// been set.
var timeOffset int64

//go:linkname now time.now
func now() (sec int64, nsec int32, mono int64) {
	mono = nanotime()
	sec = (mono + timeOffset) / (1000 * 1000 * 1000)
	nsec = int32((mono + timeOffset) - sec*(1000*1000*1000))
	return
}

var stdoutBuffer = make([]byte, 120)
var position = 0

func putchar(c byte) {
	if c == '\n' || position >= len(stdoutBuffer) {
		svcOutputDebugString(&stdoutBuffer[0], uint64(position))
		position = 0
		return
	}

	stdoutBuffer[position] = c
	position++
}

func buffered() int {
	return 0
}

func getchar() byte {
	return 0
}

func abort() {
	for {
		exit(1)
	}
}

//export write
func libc_write(fd int32, buf *byte, count int) int {
	// TODO: Proper handling write
	for i := 0; i < count; i++ {
		putchar(*buf)
		buf = (*byte)(unsafe.Add(unsafe.Pointer(buf), 1))
	}
	return count
}

// exit checks if a savedReturnAddress were provided by the launcher
// if so, calls the nxExit which restores the stack and returns to launcher
// otherwise just calls systemcall exit
func exit(code int) {
	if savedReturnAddress == 0 {
		svcExitProcess(code)
		return
	}

	nxExit(code, stackTop, savedReturnAddress)
}

type configEntry struct {
	Key   uint32
	Flags uint32
	Value [2]uint64
}

func setupEnv() {
	if debugInit {
		println("Saved Return Address:", savedReturnAddress)
		println("Context:", context)
		println("Main Thread Handle:", mainThread)
	}

	// See https://switchbrew.org/w/index.php?title=Homebrew_ABI
	// Here we parse only the required configs for initializing
	if context != 0 {
		ptr := context
		entry := (*configEntry)(unsafe.Pointer(ptr))
		for entry.Key != envEntryTypeEndOfList {
			switch entry.Key {
			case envEntryTypeOverrideHeap:
				if debugInit {
					println("Got heap override")
				}
				heapStart = uintptr(entry.Value[0])
				heapEnd = heapStart + uintptr(entry.Value[1])
			case envEntryTypeMainThreadHandle:
				mainThread = uintptr(entry.Value[0])
			default:
				if entry.Flags&1 > 0 {
					// Mandatory but not parsed
					runtimePanic("mandatory config entry not parsed")
				}
			}
			ptr += unsafe.Sizeof(configEntry{})
			entry = (*configEntry)(unsafe.Pointer(ptr))
		}
	}
	// Fetch used / total RAM for allocating HEAP
	svcGetInfo(&totalRam, infoTypeTotalMemorySize, currentProcessHandle, 0)
	svcGetInfo(&usedRam, infoTypeUsedMemorySize, currentProcessHandle, 0)
}

func setupHeap() {
	if heapStart != 0 {
		if debugInit {
			print("Heap already overridden by hblauncher")
		}
		// Already overridden
		return
	}

	if debugInit {
		print("No heap override. Using normal initialization")
	}

	size := uint32(defaultHeapSize)

	if totalRam > usedRam+0x200000 {
		// Get maximum possible heap
		size = uint32(totalRam-usedRam-0x200000) & ^uint32(0x1FFFFF)
	}

	if size < defaultHeapSize {
		size = defaultHeapSize
	}

	if debugInit {
		println("Trying to allocate", size, "bytes of heap")
	}

	svcSetHeapSize(&heapStart, uint64(size))

	if heapStart == 0 {
		runtimePanic("failed to allocate heap")
	}

	totalHeap = uint64(size)

	heapEnd = heapStart + uintptr(size)

	if debugInit {
		println("Heap Start", heapStart)
		println("Heap End  ", heapEnd)
		println("Total Heap", totalHeap)
	}
}

// growHeap tries to grow the heap size. It returns true if it succeeds, false
// otherwise.
func growHeap() bool {
	// Growing the heap is unimplemented.
	return false
}

// getHeapBase returns the start address of the heap
// this is externally linked by gonx
func getHeapBase() uintptr {
	return heapStart
}

// getHeapEnd returns the end address of the heap
// this is externally linked by gonx
func getHeapEnd() uintptr {
	return heapEnd
}

//go:extern __data_start
var dataStartSymbol [0]byte

//go:extern __data_end
var dataEndSymbol [0]byte

//go:extern __bss_start
var bssStartSymbol [0]byte

//go:extern __bss_end
var bssEndSymbol [0]byte

// Find global variables.
// The linker script provides __*_start and __*_end symbols that can be used to
// scan the given sections. They are already aligned so don't need to be
// manually aligned here.
func findGlobals(found func(start, end uintptr)) {
	dataStart := uintptr(unsafe.Pointer(&dataStartSymbol))
	dataEnd := uintptr(unsafe.Pointer(&dataEndSymbol))
	found(dataStart, dataEnd)
	bssStart := uintptr(unsafe.Pointer(&bssStartSymbol))
	bssEnd := uintptr(unsafe.Pointer(&bssEndSymbol))
	found(bssStart, bssEnd)
}

// getContextPtr returns the hblauncher context
// this is externally linked by gonx
func getContextPtr() uintptr {
	return context
}

// getMainThreadHandle returns the main thread handler if any
// this is externally linked by gonx
func getMainThreadHandle() uintptr {
	return mainThread
}

//export armGetSystemTick
func getArmSystemTick() int64

// nxExit exits the program to homebrew launcher
//
//export __nx_exit
func nxExit(code int, stackTop uintptr, exitFunction uintptr)

// Horizon System Calls
// svcSetHeapSize Set the process heap to a given size. It can both extend and shrink the heap.
// svc 0x01
//
//export svcSetHeapSize
func svcSetHeapSize(addr *uintptr, length uint64) uint64

// svcExitProcess Exits the current process.
// svc 0x07
//
//export svcExitProcess
func svcExitProcess(code int)

// svcSleepThread Sleeps the current thread for the specified amount of time.
// svc 0x0B
//
//export svcSleepThread
func svcSleepThread(nanos uint64)

// svcOutputDebugString Outputs debug text, if used during debugging.
// svc 0x27
//
//export svcOutputDebugString
func svcOutputDebugString(str *uint8, size uint64) uint64

// svcGetInfo Retrieves information about the system, or a certain kernel object.
// svc 0x29
//
//export svcGetInfo
func svcGetInfo(output *uint64, id0 uint32, handle uint32, id1 uint64) uint64

func hardwareRand() (n uint64, ok bool) {
	// TODO: see whether there is a RNG and use it.
	return 0, false
}

func libc_errno_location() *int32 {
	// CGo is unavailable, so this function should be unreachable.
	runtimePanic("runtime: no cgo errno")
	return nil
}