1// Copyright 2011 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package runtime
6
7import (
8	"unsafe"
9)
10
11type mOS struct{}
12
13//go:noescape
14//extern _umtx_op
15func sys_umtx_op(addr *uint32, mode int32, val uint32, uaddr1 uinptr, ts *umtx_time) int32
16
17//go:noescape
18//extern sysctl
19func sysctl(*uint32, uint32, *byte, *uintptr, *byte, uintptr) int32
20
21const (
22	_CTL_MAXNAME     = 24
23	_CPU_LEVEL_WHICH = 3
24	_CPU_WHICH_PID   = 2
25)
26
27// From FreeBSD's <sys/sysctl.h>
28const (
29	_CTL_HW      = 6
30	_HW_PAGESIZE = 7
31)
32
33// Undocumented numbers from FreeBSD's lib/libc/gen/sysctlnametomib.c.
34const (
35	_CTL_QUERY     = 0
36	_CTL_QUERY_MIB = 3
37)
38
39// sysctlnametomib fill mib with dynamically assigned sysctl entries of name,
40// return count of effected mib slots, return 0 on error.
41func sysctlnametomib(name []byte, mib *[_CTL_MAXNAME]uint32) uint32 {
42	oid := [2]uint32{_CTL_QUERY, _CTL_QUERY_MIB}
43	miblen := uintptr(_CTL_MAXNAME)
44	if sysctl(&oid[0], 2, (*byte)(unsafe.Pointer(mib)), &miblen, (*byte)(unsafe.Pointer(&name[0])), (uintptr)(len(name))) < 0 {
45		return 0
46	}
47	miblen /= unsafe.Sizeof(uint32(0))
48	if miblen <= 0 {
49		return 0
50	}
51	return uint32(miblen)
52}
53
54const (
55	_CPU_CURRENT_PID = -1 // Current process ID.
56)
57
58//go:noescape
59//extern cpuset_getaffinity
60func cpuset_getaffinity(level int32, which int32, id int64, size uintptr, mask *byte) int32
61
62//go:systemstack
63func getncpu() int32 {
64	// Use a large buffer for the CPU mask. We're on the system
65	// stack, so this is fine, and we can't allocate memory for a
66	// dynamically-sized buffer at this point.
67	const maxCPUs = 64 * 1024
68	var mask [maxCPUs / 8]byte
69	var mib [_CTL_MAXNAME]uint32
70
71	// According to FreeBSD's /usr/src/sys/kern/kern_cpuset.c,
72	// cpuset_getaffinity return ERANGE when provided buffer size exceed the limits in kernel.
73	// Querying kern.smp.maxcpus to calculate maximum buffer size.
74	// See https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=200802
75
76	// Variable kern.smp.maxcpus introduced at Dec 23 2003, revision 123766,
77	// with dynamically assigned sysctl entries.
78	miblen := sysctlnametomib([]byte("kern.smp.maxcpus"), &mib)
79	if miblen == 0 {
80		return 1
81	}
82
83	// Query kern.smp.maxcpus.
84	dstsize := uintptr(4)
85	maxcpus := uint32(0)
86	if sysctl(&mib[0], miblen, (*byte)(unsafe.Pointer(&maxcpus)), &dstsize, nil, 0) != 0 {
87		return 1
88	}
89
90	maskSize := uintptr(int(maxcpus+7) / 8)
91	if maskSize < sys.PtrSize {
92		maskSize = sys.PtrSize
93	}
94	if maskSize > uintptr(len(mask)) {
95		maskSize = uintptr(len(mask))
96	}
97
98	if cpuset_getaffinity(_CPU_LEVEL_WHICH, _CPU_WHICH_PID, _CPU_CURRENT_PID,
99		maskSize, (*byte)(unsafe.Pointer(&mask[0]))) != 0 {
100		return 1
101	}
102	n := int32(0)
103	for _, v := range mask[:maskSize] {
104		for v != 0 {
105			n += int32(v & 1)
106			v >>= 1
107		}
108	}
109	if n == 0 {
110		return 1
111	}
112	return n
113}
114
115func getPageSize() uintptr {
116	mib := [2]uint32{_CTL_HW, _HW_PAGESIZE}
117	out := uint32(0)
118	nout := unsafe.Sizeof(out)
119	ret := sysctl(&mib[0], 2, (*byte)(unsafe.Pointer(&out)), &nout, nil, 0)
120	if ret >= 0 {
121		return uintptr(out)
122	}
123	return 0
124}
125
126// FreeBSD's umtx_op syscall is effectively the same as Linux's futex, and
127// thus the code is largely similar. See Linux implementation
128// and lock_futex.go for comments.
129
130//go:nosplit
131func futexsleep(addr *uint32, val uint32, ns int64) {
132	systemstack(func() {
133		futexsleep1(addr, val, ns)
134	})
135}
136
137func futexsleep1(addr *uint32, val uint32, ns int64) {
138	var utp *umtx_time
139	if ns >= 0 {
140		var ut umtx_time
141		ut._clockid = _CLOCK_MONOTONIC
142		ut._timeout.setNsec(ns)
143		utp = &ut
144	}
145	ret := sys_umtx_op(addr, _UMTX_OP_WAIT_UINT_PRIVATE, val, unsafe.Sizeof(*utp), utp)
146	if ret >= 0 || ret == -_EINTR {
147		return
148	}
149	print("umtx_wait addr=", addr, " val=", val, " ret=", ret, "\n")
150	*(*int32)(unsafe.Pointer(uintptr(0x1005))) = 0x1005
151}
152
153//go:nosplit
154func futexwakeup(addr *uint32, cnt uint32) {
155	ret := sys_umtx_op(addr, _UMTX_OP_WAKE_PRIVATE, cnt, 0, nil)
156	if ret >= 0 {
157		return
158	}
159
160	systemstack(func() {
161		print("umtx_wake_addr=", addr, " ret=", ret, "\n")
162	})
163}
164
165func osinit() {
166	ncpu = getncpu()
167	if physPageSize == 0 {
168		physPageSize = getPageSize()
169	}
170}
171
172func sysargs(argc int32, argv **byte) {
173	n := argc + 1
174
175	// skip over argv, envp to get to auxv
176	for argv_index(argv, n) != nil {
177		n++
178	}
179
180	// skip NULL separator
181	n++
182
183	// now argv+n is auxv
184	auxv := (*[1 << 28]uintptr)(add(unsafe.Pointer(argv), uintptr(n)*sys.PtrSize))
185	sysauxv(auxv[:])
186}
187
188const (
189	_AT_NULL     = 0  // Terminates the vector
190	_AT_PAGESZ   = 6  // Page size in bytes
191	_AT_TIMEKEEP = 22 // Pointer to timehands.
192	_AT_HWCAP    = 25 // CPU feature flags
193	_AT_HWCAP2   = 26 // CPU feature flags 2
194)
195
196func sysauxv(auxv []uintptr) {
197	for i := 0; auxv[i] != _AT_NULL; i += 2 {
198		tag, val := auxv[i], auxv[i+1]
199		switch tag {
200		// _AT_NCPUS from auxv shouldn't be used due to golang.org/issue/15206
201		case _AT_PAGESZ:
202			physPageSize = val
203		case _AT_TIMEKEEP:
204			timekeepSharedPage = (*vdsoTimekeep)(unsafe.Pointer(val))
205		}
206
207		archauxv(tag, val)
208	}
209}
210