cpu: add CPU features for s390x

Add basic support for IBM Z (s390x) CPU feature (known as
'facilities') detection.

Note that some of these features are mandatory when using the Go
compiler (for example, ldisp and eimm) but aren't mandatory when
using gccgo.

Cryptographic function detection is not yet implemented for
gccgo.

Change-Id: Ic6494d0df0bc1c1ad1713c9ff11ae23fba03d215
Reviewed-on: https://go-review.googlesource.com/c/163003
Reviewed-by: Tobias Klauser <tobias.klauser@gmail.com>
Run-TryBot: Tobias Klauser <tobias.klauser@gmail.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
diff --git a/cpu/cpu.go b/cpu/cpu.go
index e3a2c4c..679e78c 100644
--- a/cpu/cpu.go
+++ b/cpu/cpu.go
@@ -94,3 +94,33 @@
 	IsPOWER9 bool // ISA v3.00 (POWER9)
 	_        CacheLinePad
 }
+
+// S390X contains the supported CPU features of the current IBM Z
+// (s390x) platform. If the current platform is not IBM Z then all
+// feature flags are false.
+//
+// S390X is padded to avoid false sharing. Further HasVX is only set
+// if the OS supports vector registers in addition to the STFLE
+// feature bit being set.
+var S390X struct {
+	_         CacheLinePad
+	HasZARCH  bool // z/Architecture mode is active [mandatory]
+	HasSTFLE  bool // store facility list extended
+	HasLDISP  bool // long (20-bit) displacements
+	HasEIMM   bool // 32-bit immediates
+	HasDFP    bool // decimal floating point
+	HasETF3EH bool // ETF-3 enhanced
+	HasMSA    bool // message security assist (CPACF)
+	HasAES    bool // KM-AES{128,192,256} functions
+	HasAESCBC bool // KMC-AES{128,192,256} functions
+	HasAESCTR bool // KMCTR-AES{128,192,256} functions
+	HasAESGCM bool // KMA-GCM-AES{128,192,256} functions
+	HasGHASH  bool // KIMD-GHASH function
+	HasSHA1   bool // K{I,L}MD-SHA-1 functions
+	HasSHA256 bool // K{I,L}MD-SHA-256 functions
+	HasSHA512 bool // K{I,L}MD-SHA-512 functions
+	HasSHA3   bool // K{I,L}MD-SHA3-{224,256,384,512} and K{I,L}MD-SHAKE-{128,256} functions
+	HasVX     bool // vector facility
+	HasVXE    bool // vector-enhancements facility 1
+	_         CacheLinePad
+}
diff --git a/cpu/cpu_gc_s390x.go b/cpu/cpu_gc_s390x.go
new file mode 100644
index 0000000..568bcd0
--- /dev/null
+++ b/cpu/cpu_gc_s390x.go
@@ -0,0 +1,21 @@
+// Copyright 2019 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.
+
+// +build !gccgo
+
+package cpu
+
+// haveAsmFunctions reports whether the other functions in this file can
+// be safely called.
+func haveAsmFunctions() bool { return true }
+
+// The following feature detection functions are defined in cpu_s390x.s.
+// They are likely to be expensive to call so the results should be cached.
+func stfle() facilityList
+func kmQuery() queryResult
+func kmcQuery() queryResult
+func kmctrQuery() queryResult
+func kmaQuery() queryResult
+func kimdQuery() queryResult
+func klmdQuery() queryResult
diff --git a/cpu/cpu_gccgo_s390x.go b/cpu/cpu_gccgo_s390x.go
new file mode 100644
index 0000000..aa986f7
--- /dev/null
+++ b/cpu/cpu_gccgo_s390x.go
@@ -0,0 +1,22 @@
+// Copyright 2019 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.
+
+// +build gccgo
+
+package cpu
+
+// haveAsmFunctions reports whether the other functions in this file can
+// be safely called.
+func haveAsmFunctions() bool { return false }
+
+// TODO(mundaym): the following feature detection functions are currently
+// stubs. See https://golang.org/cl/162887 for how to fix this.
+// They are likely to be expensive to call so the results should be cached.
+func stfle() facilityList     { panic("not implemented for gccgo") }
+func kmQuery() queryResult    { panic("not implemented for gccgo") }
+func kmcQuery() queryResult   { panic("not implemented for gccgo") }
+func kmctrQuery() queryResult { panic("not implemented for gccgo") }
+func kmaQuery() queryResult   { panic("not implemented for gccgo") }
+func kimdQuery() queryResult  { panic("not implemented for gccgo") }
+func klmdQuery() queryResult  { panic("not implemented for gccgo") }
diff --git a/cpu/cpu_linux_s390x.go b/cpu/cpu_linux_s390x.go
new file mode 100644
index 0000000..d579eae
--- /dev/null
+++ b/cpu/cpu_linux_s390x.go
@@ -0,0 +1,161 @@
+// Copyright 2019 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
+
+const cacheLineSize = 256
+
+const (
+	// bit mask values from /usr/include/bits/hwcap.h
+	hwcap_ZARCH  = 2
+	hwcap_STFLE  = 4
+	hwcap_MSA    = 8
+	hwcap_LDISP  = 16
+	hwcap_EIMM   = 32
+	hwcap_DFP    = 64
+	hwcap_ETF3EH = 256
+	hwcap_VX     = 2048
+	hwcap_VXE    = 8192
+)
+
+// bitIsSet reports whether the bit at index is set. The bit index
+// is in big endian order, so bit index 0 is the leftmost bit.
+func bitIsSet(bits []uint64, index uint) bool {
+	return bits[index/64]&((1<<63)>>(index%64)) != 0
+}
+
+// function is the code for the named cryptographic function.
+type function uint8
+
+const (
+	// KM{,A,C,CTR} function codes
+	aes128 function = 18 // AES-128
+	aes192 function = 19 // AES-192
+	aes256 function = 20 // AES-256
+
+	// K{I,L}MD function codes
+	sha1     function = 1  // SHA-1
+	sha256   function = 2  // SHA-256
+	sha512   function = 3  // SHA-512
+	sha3_224 function = 32 // SHA3-224
+	sha3_256 function = 33 // SHA3-256
+	sha3_384 function = 34 // SHA3-384
+	sha3_512 function = 35 // SHA3-512
+	shake128 function = 36 // SHAKE-128
+	shake256 function = 37 // SHAKE-256
+
+	// KLMD function codes
+	ghash function = 65 // GHASH
+)
+
+// queryResult contains the result of a Query function
+// call. Bits are numbered in big endian order so the
+// leftmost bit (the MSB) is at index 0.
+type queryResult struct {
+	bits [2]uint64
+}
+
+// Has reports whether the given functions are present.
+func (q *queryResult) Has(fns ...function) bool {
+	if len(fns) == 0 {
+		panic("no function codes provided")
+	}
+	for _, f := range fns {
+		if !bitIsSet(q.bits[:], uint(f)) {
+			return false
+		}
+	}
+	return true
+}
+
+// facility is a bit index for the named facility.
+type facility uint8
+
+const (
+	// cryptography facilities
+	msa4 facility = 77  // message-security-assist extension 4
+	msa8 facility = 146 // message-security-assist extension 8
+)
+
+// facilityList contains the result of an STFLE call.
+// Bits are numbered in big endian order so the
+// leftmost bit (the MSB) is at index 0.
+type facilityList struct {
+	bits [4]uint64
+}
+
+// Has reports whether the given facilities are present.
+func (s *facilityList) Has(fs ...facility) bool {
+	if len(fs) == 0 {
+		panic("no facility bits provided")
+	}
+	for _, f := range fs {
+		if !bitIsSet(s.bits[:], uint(f)) {
+			return false
+		}
+	}
+	return true
+}
+
+func doinit() {
+	// test HWCAP bit vector
+	has := func(featureMask uint) bool {
+		return hwCap&featureMask == featureMask
+	}
+
+	// mandatory
+	S390X.HasZARCH = has(hwcap_ZARCH)
+
+	// optional
+	S390X.HasSTFLE = has(hwcap_STFLE)
+	S390X.HasLDISP = has(hwcap_LDISP)
+	S390X.HasEIMM = has(hwcap_EIMM)
+	S390X.HasETF3EH = has(hwcap_ETF3EH)
+	S390X.HasDFP = has(hwcap_DFP)
+	S390X.HasMSA = has(hwcap_MSA)
+	S390X.HasVX = has(hwcap_VX)
+	if S390X.HasVX {
+		S390X.HasVXE = has(hwcap_VXE)
+	}
+
+	// We need implementations of stfle, km and so on
+	// to detect cryptographic features.
+	if !haveAsmFunctions() {
+		return
+	}
+
+	// optional cryptographic functions
+	if S390X.HasMSA {
+		aes := []function{aes128, aes192, aes256}
+
+		// cipher message
+		km, kmc := kmQuery(), kmcQuery()
+		S390X.HasAES = km.Has(aes...)
+		S390X.HasAESCBC = kmc.Has(aes...)
+		if S390X.HasSTFLE {
+			facilities := stfle()
+			if facilities.Has(msa4) {
+				kmctr := kmctrQuery()
+				S390X.HasAESCTR = kmctr.Has(aes...)
+			}
+			if facilities.Has(msa8) {
+				kma := kmaQuery()
+				S390X.HasAESGCM = kma.Has(aes...)
+			}
+		}
+
+		// compute message digest
+		kimd := kimdQuery() // intermediate (no padding)
+		klmd := klmdQuery() // last (padding)
+		S390X.HasSHA1 = kimd.Has(sha1) && klmd.Has(sha1)
+		S390X.HasSHA256 = kimd.Has(sha256) && klmd.Has(sha256)
+		S390X.HasSHA512 = kimd.Has(sha512) && klmd.Has(sha512)
+		S390X.HasGHASH = kimd.Has(ghash) // KLMD-GHASH does not exist
+		sha3 := []function{
+			sha3_224, sha3_256, sha3_384, sha3_512,
+			shake128, shake256,
+		}
+		S390X.HasSHA3 = kimd.Has(sha3...) && klmd.Has(sha3...)
+	}
+}
diff --git a/cpu/cpu_s390x.go b/cpu/cpu_s390x.go
deleted file mode 100644
index ce8a228..0000000
--- a/cpu/cpu_s390x.go
+++ /dev/null
@@ -1,9 +0,0 @@
-// Copyright 2018 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
-
-const cacheLineSize = 256
-
-func doinit() {}
diff --git a/cpu/cpu_s390x.s b/cpu/cpu_s390x.s
new file mode 100644
index 0000000..e5037d9
--- /dev/null
+++ b/cpu/cpu_s390x.s
@@ -0,0 +1,57 @@
+// Copyright 2019 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.
+
+// +build !gccgo
+
+#include "textflag.h"
+
+// func stfle() facilityList
+TEXT ·stfle(SB), NOSPLIT|NOFRAME, $0-32
+	MOVD $ret+0(FP), R1
+	MOVD $3, R0          // last doubleword index to store
+	XC   $32, (R1), (R1) // clear 4 doublewords (32 bytes)
+	WORD $0xb2b01000     // store facility list extended (STFLE)
+	RET
+
+// func kmQuery() queryResult
+TEXT ·kmQuery(SB), NOSPLIT|NOFRAME, $0-16
+	MOVD $0, R0         // set function code to 0 (KM-Query)
+	MOVD $ret+0(FP), R1 // address of 16-byte return value
+	WORD $0xB92E0024    // cipher message (KM)
+	RET
+
+// func kmcQuery() queryResult
+TEXT ·kmcQuery(SB), NOSPLIT|NOFRAME, $0-16
+	MOVD $0, R0         // set function code to 0 (KMC-Query)
+	MOVD $ret+0(FP), R1 // address of 16-byte return value
+	WORD $0xB92F0024    // cipher message with chaining (KMC)
+	RET
+
+// func kmctrQuery() queryResult
+TEXT ·kmctrQuery(SB), NOSPLIT|NOFRAME, $0-16
+	MOVD $0, R0         // set function code to 0 (KMCTR-Query)
+	MOVD $ret+0(FP), R1 // address of 16-byte return value
+	WORD $0xB92D4024    // cipher message with counter (KMCTR)
+	RET
+
+// func kmaQuery() queryResult
+TEXT ·kmaQuery(SB), NOSPLIT|NOFRAME, $0-16
+	MOVD $0, R0         // set function code to 0 (KMA-Query)
+	MOVD $ret+0(FP), R1 // address of 16-byte return value
+	WORD $0xb9296024    // cipher message with authentication (KMA)
+	RET
+
+// func kimdQuery() queryResult
+TEXT ·kimdQuery(SB), NOSPLIT|NOFRAME, $0-16
+	MOVD $0, R0         // set function code to 0 (KIMD-Query)
+	MOVD $ret+0(FP), R1 // address of 16-byte return value
+	WORD $0xB93E0024    // compute intermediate message digest (KIMD)
+	RET
+
+// func klmdQuery() queryResult
+TEXT ·klmdQuery(SB), NOSPLIT|NOFRAME, $0-16
+	MOVD $0, R0         // set function code to 0 (KLMD-Query)
+	MOVD $ret+0(FP), R1 // address of 16-byte return value
+	WORD $0xB93F0024    // compute last message digest (KLMD)
+	RET
diff --git a/cpu/cpu_test.go b/cpu/cpu_test.go
index c0d8f64..0433d55 100644
--- a/cpu/cpu_test.go
+++ b/cpu/cpu_test.go
@@ -55,3 +55,20 @@
 		}
 	}
 }
+
+func TestS390X(t *testing.T) {
+	if runtime.GOARCH != "s390x" {
+		return
+	}
+	if testing.Verbose() {
+		t.Logf("%+v\n", cpu.S390X)
+	}
+	// z/Architecture is mandatory
+	if !cpu.S390X.HasZARCH {
+		t.Error("HasZARCH expected true, got false")
+	}
+	// vector-enhancements require vector facility to be enabled
+	if cpu.S390X.HasVXE && !cpu.S390X.HasVX {
+		t.Error("HasVX expected true, got false (VXE is true)")
+	}
+}