cpu_linux_riscv64.go

  1// Copyright 2024 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 cpu
  6
  7import (
  8	"syscall"
  9	"unsafe"
 10)
 11
 12// RISC-V extension discovery code for Linux. The approach here is to first try the riscv_hwprobe
 13// syscall falling back to HWCAP to check for the C extension if riscv_hwprobe is not available.
 14//
 15// A note on detection of the Vector extension using HWCAP.
 16//
 17// Support for the Vector extension version 1.0 was added to the Linux kernel in release 6.5.
 18// Support for the riscv_hwprobe syscall was added in 6.4. It follows that if the riscv_hwprobe
 19// syscall is not available then neither is the Vector extension (which needs kernel support).
 20// The riscv_hwprobe syscall should then be all we need to detect the Vector extension.
 21// However, some RISC-V board manufacturers ship boards with an older kernel on top of which
 22// they have back-ported various versions of the Vector extension patches but not the riscv_hwprobe
 23// patches. These kernels advertise support for the Vector extension using HWCAP. Falling
 24// back to HWCAP to detect the Vector extension, if riscv_hwprobe is not available, or simply not
 25// bothering with riscv_hwprobe at all and just using HWCAP may then seem like an attractive option.
 26//
 27// Unfortunately, simply checking the 'V' bit in AT_HWCAP will not work as this bit is used by
 28// RISC-V board and cloud instance providers to mean different things. The Lichee Pi 4A board
 29// and the Scaleway RV1 cloud instances use the 'V' bit to advertise their support for the unratified
 30// 0.7.1 version of the Vector Specification. The Banana Pi BPI-F3 and the CanMV-K230 board use
 31// it to advertise support for 1.0 of the Vector extension. Versions 0.7.1 and 1.0 of the Vector
 32// extension are binary incompatible. HWCAP can then not be used in isolation to populate the
 33// HasV field as this field indicates that the underlying CPU is compatible with RVV 1.0.
 34//
 35// There is a way at runtime to distinguish between versions 0.7.1 and 1.0 of the Vector
 36// specification by issuing a RVV 1.0 vsetvli instruction and checking the vill bit of the vtype
 37// register. This check would allow us to safely detect version 1.0 of the Vector extension
 38// with HWCAP, if riscv_hwprobe were not available. However, the check cannot
 39// be added until the assembler supports the Vector instructions.
 40//
 41// Note the riscv_hwprobe syscall does not suffer from these ambiguities by design as all of the
 42// extensions it advertises support for are explicitly versioned. It's also worth noting that
 43// the riscv_hwprobe syscall is the only way to detect multi-letter RISC-V extensions, e.g., Zba.
 44// These cannot be detected using HWCAP and so riscv_hwprobe must be used to detect the majority
 45// of RISC-V extensions.
 46//
 47// Please see https://docs.kernel.org/arch/riscv/hwprobe.html for more information.
 48
 49// golang.org/x/sys/cpu is not allowed to depend on golang.org/x/sys/unix so we must
 50// reproduce the constants, types and functions needed to make the riscv_hwprobe syscall
 51// here.
 52
 53const (
 54	// Copied from golang.org/x/sys/unix/ztypes_linux_riscv64.go.
 55	riscv_HWPROBE_KEY_IMA_EXT_0   = 0x4
 56	riscv_HWPROBE_IMA_C           = 0x2
 57	riscv_HWPROBE_IMA_V           = 0x4
 58	riscv_HWPROBE_EXT_ZBA         = 0x8
 59	riscv_HWPROBE_EXT_ZBB         = 0x10
 60	riscv_HWPROBE_EXT_ZBS         = 0x20
 61	riscv_HWPROBE_EXT_ZVBB        = 0x20000
 62	riscv_HWPROBE_EXT_ZVBC        = 0x40000
 63	riscv_HWPROBE_EXT_ZVKB        = 0x80000
 64	riscv_HWPROBE_EXT_ZVKG        = 0x100000
 65	riscv_HWPROBE_EXT_ZVKNED      = 0x200000
 66	riscv_HWPROBE_EXT_ZVKNHB      = 0x800000
 67	riscv_HWPROBE_EXT_ZVKSED      = 0x1000000
 68	riscv_HWPROBE_EXT_ZVKSH       = 0x2000000
 69	riscv_HWPROBE_EXT_ZVKT        = 0x4000000
 70	riscv_HWPROBE_KEY_CPUPERF_0   = 0x5
 71	riscv_HWPROBE_MISALIGNED_FAST = 0x3
 72	riscv_HWPROBE_MISALIGNED_MASK = 0x7
 73)
 74
 75const (
 76	// sys_RISCV_HWPROBE is copied from golang.org/x/sys/unix/zsysnum_linux_riscv64.go.
 77	sys_RISCV_HWPROBE = 258
 78)
 79
 80// riscvHWProbePairs is copied from golang.org/x/sys/unix/ztypes_linux_riscv64.go.
 81type riscvHWProbePairs struct {
 82	key   int64
 83	value uint64
 84}
 85
 86const (
 87	// CPU features
 88	hwcap_RISCV_ISA_C = 1 << ('C' - 'A')
 89)
 90
 91func doinit() {
 92	// A slice of key/value pair structures is passed to the RISCVHWProbe syscall. The key
 93	// field should be initialised with one of the key constants defined above, e.g.,
 94	// RISCV_HWPROBE_KEY_IMA_EXT_0. The syscall will set the value field to the appropriate value.
 95	// If the kernel does not recognise a key it will set the key field to -1 and the value field to 0.
 96
 97	pairs := []riscvHWProbePairs{
 98		{riscv_HWPROBE_KEY_IMA_EXT_0, 0},
 99		{riscv_HWPROBE_KEY_CPUPERF_0, 0},
100	}
101
102	// This call only indicates that extensions are supported if they are implemented on all cores.
103	if riscvHWProbe(pairs, 0) {
104		if pairs[0].key != -1 {
105			v := uint(pairs[0].value)
106			RISCV64.HasC = isSet(v, riscv_HWPROBE_IMA_C)
107			RISCV64.HasV = isSet(v, riscv_HWPROBE_IMA_V)
108			RISCV64.HasZba = isSet(v, riscv_HWPROBE_EXT_ZBA)
109			RISCV64.HasZbb = isSet(v, riscv_HWPROBE_EXT_ZBB)
110			RISCV64.HasZbs = isSet(v, riscv_HWPROBE_EXT_ZBS)
111			RISCV64.HasZvbb = isSet(v, riscv_HWPROBE_EXT_ZVBB)
112			RISCV64.HasZvbc = isSet(v, riscv_HWPROBE_EXT_ZVBC)
113			RISCV64.HasZvkb = isSet(v, riscv_HWPROBE_EXT_ZVKB)
114			RISCV64.HasZvkg = isSet(v, riscv_HWPROBE_EXT_ZVKG)
115			RISCV64.HasZvkt = isSet(v, riscv_HWPROBE_EXT_ZVKT)
116			// Cryptography shorthand extensions
117			RISCV64.HasZvkn = isSet(v, riscv_HWPROBE_EXT_ZVKNED) &&
118				isSet(v, riscv_HWPROBE_EXT_ZVKNHB) && RISCV64.HasZvkb && RISCV64.HasZvkt
119			RISCV64.HasZvknc = RISCV64.HasZvkn && RISCV64.HasZvbc
120			RISCV64.HasZvkng = RISCV64.HasZvkn && RISCV64.HasZvkg
121			RISCV64.HasZvks = isSet(v, riscv_HWPROBE_EXT_ZVKSED) &&
122				isSet(v, riscv_HWPROBE_EXT_ZVKSH) && RISCV64.HasZvkb && RISCV64.HasZvkt
123			RISCV64.HasZvksc = RISCV64.HasZvks && RISCV64.HasZvbc
124			RISCV64.HasZvksg = RISCV64.HasZvks && RISCV64.HasZvkg
125		}
126		if pairs[1].key != -1 {
127			v := pairs[1].value & riscv_HWPROBE_MISALIGNED_MASK
128			RISCV64.HasFastMisaligned = v == riscv_HWPROBE_MISALIGNED_FAST
129		}
130	}
131
132	// Let's double check with HWCAP if the C extension does not appear to be supported.
133	// This may happen if we're running on a kernel older than 6.4.
134
135	if !RISCV64.HasC {
136		RISCV64.HasC = isSet(hwCap, hwcap_RISCV_ISA_C)
137	}
138}
139
140func isSet(hwc uint, value uint) bool {
141	return hwc&value != 0
142}
143
144// riscvHWProbe is a simplified version of the generated wrapper function found in
145// golang.org/x/sys/unix/zsyscall_linux_riscv64.go. We simplify it by removing the
146// cpuCount and cpus parameters which we do not need. We always want to pass 0 for
147// these parameters here so the kernel only reports the extensions that are present
148// on all cores.
149func riscvHWProbe(pairs []riscvHWProbePairs, flags uint) bool {
150	var _zero uintptr
151	var p0 unsafe.Pointer
152	if len(pairs) > 0 {
153		p0 = unsafe.Pointer(&pairs[0])
154	} else {
155		p0 = unsafe.Pointer(&_zero)
156	}
157
158	_, _, e1 := syscall.Syscall6(sys_RISCV_HWPROBE, uintptr(p0), uintptr(len(pairs)), uintptr(0), uintptr(0), uintptr(flags), 0)
159	return e1 == 0
160}