cpu: add support for detecting cpu features on loong64

Except for lasx, all other features have been implemented
in the Go mainline.

Change-Id: I61a09396ed23d17991b641a1265e952585cb5636
Reviewed-on: https://go-review.googlesource.com/c/sys/+/655355
Reviewed-by: David Chase <drchase@google.com>
Reviewed-by: sophie zhao <zhaoxiaolin@loongson.cn>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Junyang Shao <shaojunyang@google.com>
Reviewed-by: Meidan Li <limeidan@loongson.cn>
diff --git a/cpu/cpu.go b/cpu/cpu.go
index 9c105f2..2e73ee1 100644
--- a/cpu/cpu.go
+++ b/cpu/cpu.go
@@ -149,6 +149,18 @@
 	_           CacheLinePad
 }
 
+// The booleans in Loong64 contain the correspondingly named cpu feature bit.
+// The struct is padded to avoid false sharing.
+var Loong64 struct {
+	_         CacheLinePad
+	HasLSX    bool // support 128-bit vector extension
+	HasLASX   bool // support 256-bit vector extension
+	HasCRC32  bool // support CRC instruction
+	HasLAM_BH bool // support AM{SWAP/ADD}[_DB].{B/H} instruction
+	HasLAMCAS bool // support AMCAS[_DB].{B/H/W/D} instruction
+	_         CacheLinePad
+}
+
 // MIPS64X contains the supported CPU features of the current mips64/mips64le
 // platforms. If the current platform is not mips64/mips64le or the current
 // operating system is not Linux then all feature flags are false.
diff --git a/cpu/cpu_linux_loong64.go b/cpu/cpu_linux_loong64.go
new file mode 100644
index 0000000..4f34114
--- /dev/null
+++ b/cpu/cpu_linux_loong64.go
@@ -0,0 +1,22 @@
+// Copyright 2025 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package cpu
+
+// HWCAP bits. These are exposed by the Linux kernel.
+const (
+	hwcap_LOONGARCH_LSX  = 1 << 4
+	hwcap_LOONGARCH_LASX = 1 << 5
+)
+
+func doinit() {
+	// TODO: Features that require kernel support like LSX and LASX can
+	// be detected here once needed in std library or by the compiler.
+	Loong64.HasLSX = hwcIsSet(hwCap, hwcap_LOONGARCH_LSX)
+	Loong64.HasLASX = hwcIsSet(hwCap, hwcap_LOONGARCH_LASX)
+}
+
+func hwcIsSet(hwc uint, val uint) bool {
+	return hwc&val != 0
+}
diff --git a/cpu/cpu_linux_noinit.go b/cpu/cpu_linux_noinit.go
index 7d902b6..a428dec 100644
--- a/cpu/cpu_linux_noinit.go
+++ b/cpu/cpu_linux_noinit.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-//go:build linux && !arm && !arm64 && !mips64 && !mips64le && !ppc64 && !ppc64le && !s390x && !riscv64
+//go:build linux && !arm && !arm64 && !loong64 && !mips64 && !mips64le && !ppc64 && !ppc64le && !s390x && !riscv64
 
 package cpu
 
diff --git a/cpu/cpu_loong64.go b/cpu/cpu_loong64.go
index 5586358..45ecb29 100644
--- a/cpu/cpu_loong64.go
+++ b/cpu/cpu_loong64.go
@@ -8,5 +8,43 @@
 
 const cacheLineSize = 64
 
+// Bit fields for CPUCFG registers, Related reference documents:
+// https://loongson.github.io/LoongArch-Documentation/LoongArch-Vol1-EN.html#_cpucfg
+const (
+	// CPUCFG1 bits
+	cpucfg1_CRC32 = 1 << 25
+
+	// CPUCFG2 bits
+	cpucfg2_LAM_BH = 1 << 27
+	cpucfg2_LAMCAS = 1 << 28
+)
+
 func initOptions() {
+	options = []option{
+		{Name: "lsx", Feature: &Loong64.HasLSX},
+		{Name: "lasx", Feature: &Loong64.HasLASX},
+		{Name: "crc32", Feature: &Loong64.HasCRC32},
+		{Name: "lam_bh", Feature: &Loong64.HasLAM_BH},
+		{Name: "lamcas", Feature: &Loong64.HasLAMCAS},
+	}
+
+	// The CPUCFG data on Loong64 only reflects the hardware capabilities,
+	// not the kernel support status, so features such as LSX and LASX that
+	// require kernel support cannot be obtained from the CPUCFG data.
+	//
+	// These features only require hardware capability support and do not
+	// require kernel specific support, so they can be obtained directly
+	// through CPUCFG
+	cfg1 := get_cpucfg(1)
+	cfg2 := get_cpucfg(2)
+
+	Loong64.HasCRC32 = cfgIsSet(cfg1, cpucfg1_CRC32)
+	Loong64.HasLAMCAS = cfgIsSet(cfg2, cpucfg2_LAMCAS)
+	Loong64.HasLAM_BH = cfgIsSet(cfg2, cpucfg2_LAM_BH)
+}
+
+func get_cpucfg(reg uint32) uint32
+
+func cfgIsSet(cfg uint32, val uint32) bool {
+	return cfg&val != 0
 }
diff --git a/cpu/cpu_loong64.s b/cpu/cpu_loong64.s
new file mode 100644
index 0000000..30b4c0d
--- /dev/null
+++ b/cpu/cpu_loong64.s
@@ -0,0 +1,12 @@
+// Copyright 2025 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+#include "textflag.h"
+
+// func get_cpucfg(reg uint32) uint32
+TEXT ·get_cpucfg(SB), NOSPLIT|NOFRAME, $0
+	MOVW	reg+0(FP), R5
+	CPUCFG	R5, R4
+	MOVW	R4, ret+8(FP)
+	RET
diff --git a/cpu/cpu_test.go b/cpu/cpu_test.go
index dd493ec..6906786 100644
--- a/cpu/cpu_test.go
+++ b/cpu/cpu_test.go
@@ -87,6 +87,14 @@
 	}
 }
 
+func TestLOONG64Initialized(t *testing.T) {
+	if runtime.GOARCH == "loong64" {
+		if !cpu.Initialized {
+			t.Fatal("Initialized expected true, got false")
+		}
+	}
+}
+
 func TestMIPS64Initialized(t *testing.T) {
 	if runtime.GOARCH == "mips64" || runtime.GOARCH == "mips64le" {
 		if !cpu.Initialized {