// Copyright 2020 The gVisor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// +build arm64

package cpuid

import (
	"bytes"
	"encoding/binary"
	"fmt"
	"io/ioutil"
	"strconv"
	"strings"

	"gvisor.dev/gvisor/pkg/log"
)

// ARM64 doesn't have a 'cpuid' equivalent, which means it have no architected
// discovery mechanism for hardware features available to userspace code at EL0.
// The kernel exposes the presence of these features to userspace through a set
// of flags(HWCAP/HWCAP2) bits, exposed in the auxilliary vector.
// Ref Documentation/arm64/elf_hwcaps.rst for more info.
//
// Currently, only the HWCAP bits are supported.

const (
	// ARM64FeatureFP indicates support for single and double precision
	// float point types.
	ARM64FeatureFP Feature = iota

	// ARM64FeatureASIMD indicates support for Advanced SIMD with single
	// and double precision float point arithmetic.
	ARM64FeatureASIMD

	// ARM64FeatureEVTSTRM indicates support for the generic timer
	// configured to generate events at a frequency of approximately
	// 100KHz.
	ARM64FeatureEVTSTRM

	// ARM64FeatureAES indicates support for AES instructions
	// (AESE/AESD/AESMC/AESIMC).
	ARM64FeatureAES

	// ARM64FeaturePMULL indicates support for AES instructions
	// (PMULL/PMULL2).
	ARM64FeaturePMULL

	// ARM64FeatureSHA1 indicates support for SHA1 instructions
	// (SHA1C/SHA1P/SHA1M etc).
	ARM64FeatureSHA1

	// ARM64FeatureSHA2 indicates support for SHA2 instructions
	// (SHA256H/SHA256H2/SHA256SU0 etc).
	ARM64FeatureSHA2

	// ARM64FeatureCRC32 indicates support for CRC32 instructions
	// (CRC32B/CRC32H/CRC32W etc).
	ARM64FeatureCRC32

	// ARM64FeatureATOMICS indicates support for atomic instructions
	// (LDADD/LDCLR/LDEOR/LDSET etc).
	ARM64FeatureATOMICS

	// ARM64FeatureFPHP indicates support for half precision float point
	// arithmetic.
	ARM64FeatureFPHP

	// ARM64FeatureASIMDHP indicates support for ASIMD with half precision
	// float point arithmetic.
	ARM64FeatureASIMDHP

	// ARM64FeatureCPUID indicates support for EL0 access to certain ID
	// registers is available.
	ARM64FeatureCPUID

	// ARM64FeatureASIMDRDM indicates support for SQRDMLAH and SQRDMLSH
	// instructions.
	ARM64FeatureASIMDRDM

	// ARM64FeatureJSCVT indicates support for the FJCVTZS instruction.
	ARM64FeatureJSCVT

	// ARM64FeatureFCMA indicates support for the FCMLA and FCADD
	// instructions.
	ARM64FeatureFCMA

	// ARM64FeatureLRCPC indicates support for the LDAPRB/LDAPRH/LDAPR
	// instructions.
	ARM64FeatureLRCPC

	// ARM64FeatureDCPOP indicates support for DC instruction (DC CVAP).
	ARM64FeatureDCPOP

	// ARM64FeatureSHA3 indicates support for SHA3 instructions
	// (EOR3/RAX1/XAR/BCAX).
	ARM64FeatureSHA3

	// ARM64FeatureSM3 indicates support for SM3 instructions
	// (SM3SS1/SM3TT1A/SM3TT1B).
	ARM64FeatureSM3

	// ARM64FeatureSM4 indicates support for SM4 instructions
	// (SM4E/SM4EKEY).
	ARM64FeatureSM4

	// ARM64FeatureASIMDDP indicates support for dot product instructions
	// (UDOT/SDOT).
	ARM64FeatureASIMDDP

	// ARM64FeatureSHA512 indicates support for SHA2 instructions
	// (SHA512H/SHA512H2/SHA512SU0).
	ARM64FeatureSHA512

	// ARM64FeatureSVE indicates support for Scalable Vector Extension.
	ARM64FeatureSVE

	// ARM64FeatureASIMDFHM indicates support for FMLAL and FMLSL
	// instructions.
	ARM64FeatureASIMDFHM
)

// ELF auxiliary vector tags
const (
	_AT_NULL   = 0  // End of vector
	_AT_HWCAP  = 16 // hardware capability bit vector
	_AT_HWCAP2 = 26 // hardware capability bit vector 2
)

// These should not be changed after they are initialized.
var hwCap uint

// To make emulation of /proc/cpuinfo easy, these names match the names of the
// basic features in Linux defined in arch/arm64/kernel/cpuinfo.c.
var arm64FeatureStrings = map[Feature]string{
	ARM64FeatureFP:       "fp",
	ARM64FeatureASIMD:    "asimd",
	ARM64FeatureEVTSTRM:  "evtstrm",
	ARM64FeatureAES:      "aes",
	ARM64FeaturePMULL:    "pmull",
	ARM64FeatureSHA1:     "sha1",
	ARM64FeatureSHA2:     "sha2",
	ARM64FeatureCRC32:    "crc32",
	ARM64FeatureATOMICS:  "atomics",
	ARM64FeatureFPHP:     "fphp",
	ARM64FeatureASIMDHP:  "asimdhp",
	ARM64FeatureCPUID:    "cpuid",
	ARM64FeatureASIMDRDM: "asimdrdm",
	ARM64FeatureJSCVT:    "jscvt",
	ARM64FeatureFCMA:     "fcma",
	ARM64FeatureLRCPC:    "lrcpc",
	ARM64FeatureDCPOP:    "dcpop",
	ARM64FeatureSHA3:     "sha3",
	ARM64FeatureSM3:      "sm3",
	ARM64FeatureSM4:      "sm4",
	ARM64FeatureASIMDDP:  "asimddp",
	ARM64FeatureSHA512:   "sha512",
	ARM64FeatureSVE:      "sve",
	ARM64FeatureASIMDFHM: "asimdfhm",
}

var (
	cpuFreqMHz float64
	cpuImplHex uint64
	cpuArchDec uint64
	cpuVarHex  uint64
	cpuPartHex uint64
	cpuRevDec  uint64
)

// arm64FeaturesFromString includes features from arm64FeatureStrings.
var arm64FeaturesFromString = make(map[string]Feature)

// FeatureFromString returns the Feature associated with the given feature
// string plus a bool to indicate if it could find the feature.
func FeatureFromString(s string) (Feature, bool) {
	f, b := arm64FeaturesFromString[s]
	return f, b
}

// String implements fmt.Stringer.
func (f Feature) String() string {
	if s := f.flagString(); s != "" {
		return s
	}

	return fmt.Sprintf("<cpuflag %d>", f)
}

func (f Feature) flagString() string {
	if s, ok := arm64FeatureStrings[f]; ok {
		return s
	}

	return ""
}

// FeatureSet is a set of Features for a CPU.
//
// +stateify savable
type FeatureSet struct {
	// Set is the set of features that are enabled in this FeatureSet.
	Set map[Feature]bool

	// CPUImplementer is part of the processor signature.
	CPUImplementer uint8

	// CPUArchitecture is part of the processor signature.
	CPUArchitecture uint8

	// CPUVariant is part of the processor signature.
	CPUVariant uint8

	// CPUPartnum is part of the processor signature.
	CPUPartnum uint16

	// CPURevision is part of the processor signature.
	CPURevision uint8
}

// CheckHostCompatible returns nil if fs is a subset of the host feature set.
// Noop on arm64.
func (fs *FeatureSet) CheckHostCompatible() error {
	return nil
}

// ExtendedStateSize returns the number of bytes needed to save the "extended
// state" for this processor and the boundary it must be aligned to. Extended
// state includes floating point(NEON) registers, and other cpu state that's not
// associated with the normal task context.
func (fs *FeatureSet) ExtendedStateSize() (size, align uint) {
	// ARMv8 provide 32x128bits NEON registers.
	//
	// Ref arch/arm64/include/uapi/asm/ptrace.h
	// struct user_fpsimd_state {
	//        __uint128_t     vregs[32];
	//        __u32           fpsr;
	//	  __u32           fpcr;
	//	  __u32           __reserved[2];
	// };
	return 528, 16
}

// HasFeature tests whether or not a feature is in the given feature set.
func (fs *FeatureSet) HasFeature(feature Feature) bool {
	return fs.Set[feature]
}

// UseXsave returns true if 'fs' supports the "xsave" instruction.
//
// Irrelevant on arm64.
func (fs *FeatureSet) UseXsave() bool {
	return false
}

// FlagsString prints out supported CPU "flags" field in /proc/cpuinfo.
func (fs *FeatureSet) FlagsString() string {
	var s []string
	for f, _ := range arm64FeatureStrings {
		if fs.Set[f] {
			if fstr := f.flagString(); fstr != "" {
				s = append(s, fstr)
			}
		}
	}
	return strings.Join(s, " ")
}

// WriteCPUInfoTo is to generate a section of one cpu in /proc/cpuinfo. This is
// a minimal /proc/cpuinfo, and the bogomips field is simply made up.
func (fs FeatureSet) WriteCPUInfoTo(cpu uint, b *bytes.Buffer) {
	fmt.Fprintf(b, "processor\t: %d\n", cpu)
	fmt.Fprintf(b, "BogoMIPS\t: %.02f\n", cpuFreqMHz) // It's bogus anyway.
	fmt.Fprintf(b, "Features\t\t: %s\n", fs.FlagsString())
	fmt.Fprintf(b, "CPU implementer\t: 0x%x\n", cpuImplHex)
	fmt.Fprintf(b, "CPU architecture\t: %d\n", cpuArchDec)
	fmt.Fprintf(b, "CPU variant\t: 0x%x\n", cpuVarHex)
	fmt.Fprintf(b, "CPU part\t: 0x%x\n", cpuPartHex)
	fmt.Fprintf(b, "CPU revision\t: %d\n", cpuRevDec)
	fmt.Fprintln(b, "") // The /proc/cpuinfo file ends with an extra newline.
}

// HostFeatureSet uses hwCap to get host values and construct a feature set
// that matches that of the host machine.
func HostFeatureSet() *FeatureSet {
	s := make(map[Feature]bool)

	for f, _ := range arm64FeatureStrings {
		if hwCap&(1<<f) != 0 {
			s[f] = true
		}
	}

	return &FeatureSet{
		Set:             s,
		CPUImplementer:  uint8(cpuImplHex),
		CPUArchitecture: uint8(cpuArchDec),
		CPUVariant:      uint8(cpuVarHex),
		CPUPartnum:      uint16(cpuPartHex),
		CPURevision:     uint8(cpuRevDec),
	}
}

// Reads bogomips from host /proc/cpuinfo. Must run before whitelisting.
// This value is used to create the fake /proc/cpuinfo from a FeatureSet.
func initCPUInfo() {
	cpuinfob, err := ioutil.ReadFile("/proc/cpuinfo")
	if err != nil {
		// Leave it as 0. The standalone VDSO bails out in the same way.
		log.Warningf("Could not read /proc/cpuinfo: %v", err)
		return
	}
	cpuinfo := string(cpuinfob)

	// We get the value straight from host /proc/cpuinfo.
	for _, line := range strings.Split(cpuinfo, "\n") {
		switch {
		case strings.Contains(line, "BogoMIPS"):
			{
				splitMHz := strings.Split(line, ":")
				if len(splitMHz) < 2 {
					log.Warningf("Could not read /proc/cpuinfo: malformed BogoMIPS")
					break
				}

				// If there was a problem, leave cpuFreqMHz as 0.
				var err error
				cpuFreqMHz, err = strconv.ParseFloat(strings.TrimSpace(splitMHz[1]), 64)
				if err != nil {
					log.Warningf("Could not parse BogoMIPS value %v: %v", splitMHz[1], err)
					cpuFreqMHz = 0
				}
			}
		case strings.Contains(line, "CPU implementer"):
			{
				splitImpl := strings.Split(line, ":")
				if len(splitImpl) < 2 {
					log.Warningf("Could not read /proc/cpuinfo: malformed CPU implementer")
					break
				}

				// If there was a problem, leave cpuImplHex as 0.
				var err error
				cpuImplHex, err = strconv.ParseUint(strings.TrimSpace(splitImpl[1]), 0, 64)
				if err != nil {
					log.Warningf("Could not parse CPU implementer value %v: %v", splitImpl[1], err)
					cpuImplHex = 0
				}
			}
		case strings.Contains(line, "CPU architecture"):
			{
				splitArch := strings.Split(line, ":")
				if len(splitArch) < 2 {
					log.Warningf("Could not read /proc/cpuinfo: malformed CPU architecture")
					break
				}

				// If there was a problem, leave cpuArchDec as 0.
				var err error
				cpuArchDec, err = strconv.ParseUint(strings.TrimSpace(splitArch[1]), 0, 64)
				if err != nil {
					log.Warningf("Could not parse CPU architecture value %v: %v", splitArch[1], err)
					cpuArchDec = 0
				}
			}
		case strings.Contains(line, "CPU variant"):
			{
				splitVar := strings.Split(line, ":")
				if len(splitVar) < 2 {
					log.Warningf("Could not read /proc/cpuinfo: malformed CPU variant")
					break
				}

				// If there was a problem, leave cpuVarHex as 0.
				var err error
				cpuVarHex, err = strconv.ParseUint(strings.TrimSpace(splitVar[1]), 0, 64)
				if err != nil {
					log.Warningf("Could not parse CPU variant value %v: %v", splitVar[1], err)
					cpuVarHex = 0
				}
			}
		case strings.Contains(line, "CPU part"):
			{
				splitPart := strings.Split(line, ":")
				if len(splitPart) < 2 {
					log.Warningf("Could not read /proc/cpuinfo: malformed CPU part")
					break
				}

				// If there was a problem, leave cpuPartHex as 0.
				var err error
				cpuPartHex, err = strconv.ParseUint(strings.TrimSpace(splitPart[1]), 0, 64)
				if err != nil {
					log.Warningf("Could not parse CPU part value %v: %v", splitPart[1], err)
					cpuPartHex = 0
				}
			}
		case strings.Contains(line, "CPU revision"):
			{
				splitRev := strings.Split(line, ":")
				if len(splitRev) < 2 {
					log.Warningf("Could not read /proc/cpuinfo: malformed CPU revision")
					break
				}

				// If there was a problem, leave cpuRevDec as 0.
				var err error
				cpuRevDec, err = strconv.ParseUint(strings.TrimSpace(splitRev[1]), 0, 64)
				if err != nil {
					log.Warningf("Could not parse CPU revision value %v: %v", splitRev[1], err)
					cpuRevDec = 0
				}
			}
		}
	}
}

// The auxiliary vector of a process on the Linux system can be read
// from /proc/self/auxv, and tags and values are stored as 8-bytes
// decimal key-value pairs on the 64-bit system.
//
// $ od -t d8 /proc/self/auxv
//  0000000                   33      140734615224320
//  0000020                   16           3219913727
//  0000040                    6                 4096
//  0000060                   17                  100
//  0000100                    3       94665627353152
//  0000120                    4                   56
//  0000140                    5                    9
//  0000160                    7      140425502162944
//  0000200                    8                    0
//  0000220                    9       94665627365760
//  0000240                   11                 1000
//  0000260                   12                 1000
//  0000300                   13                 1000
//  0000320                   14                 1000
//  0000340                   23                    0
//  0000360                   25      140734614619513
//  0000400                   26                    0
//  0000420                   31      140734614626284
//  0000440                   15      140734614619529
//  0000460                    0                    0
func initHwCap() {
	auxv, err := ioutil.ReadFile("/proc/self/auxv")
	if err != nil {
		log.Warningf("Could not read /proc/self/auxv: %v", err)
		return
	}

	l := len(auxv) / 16
	for i := 0; i < l; i++ {
		tag := binary.LittleEndian.Uint64(auxv[i*16:])
		val := binary.LittleEndian.Uint64(auxv[(i*16 + 8):])
		if tag == _AT_HWCAP {
			hwCap = uint(val)
			break
		}
	}
}

func initFeaturesFromString() {
	for f, s := range arm64FeatureStrings {
		arm64FeaturesFromString[s] = f
	}
}

func init() {
	initCPUInfo()
	initHwCap()
	initFeaturesFromString()
}