// Copyright 2017 Google LLC
//
// 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.

#include "cpuinfo_arm.h"

#include "filesystem_for_testing.h"
#include "gtest/gtest.h"
#include "hwcaps_for_testing.h"

namespace cpu_features {
namespace {

void DisableHardwareCapabilities() { SetHardwareCapabilities(0, 0); }

TEST(CpuinfoArmTest, FromHardwareCap) {
  SetHardwareCapabilities(ARM_HWCAP_NEON, ARM_HWCAP2_AES | ARM_HWCAP2_CRC32);
  GetEmptyFilesystem();  // disabling /proc/cpuinfo
  const auto info = GetArmInfo();
  EXPECT_TRUE(info.features.vfp);    // triggered by vfpv3
  EXPECT_TRUE(info.features.vfpv3);  // triggered by neon
  EXPECT_TRUE(info.features.neon);
  EXPECT_TRUE(info.features.aes);
  EXPECT_TRUE(info.features.crc32);

  EXPECT_FALSE(info.features.vfpv4);
  EXPECT_FALSE(info.features.iwmmxt);
  EXPECT_FALSE(info.features.crunch);
  EXPECT_FALSE(info.features.thumbee);
  EXPECT_FALSE(info.features.vfpv3d16);
  EXPECT_FALSE(info.features.idiva);
  EXPECT_FALSE(info.features.idivt);
  EXPECT_FALSE(info.features.pmull);
  EXPECT_FALSE(info.features.sha1);
  EXPECT_FALSE(info.features.sha2);

  // check some random features with EnumValue():
  EXPECT_TRUE(GetArmFeaturesEnumValue(&info.features, ARM_VFP));
  EXPECT_FALSE(GetArmFeaturesEnumValue(&info.features, ARM_VFPV4));
  // out of bound EnumValue() check
  EXPECT_FALSE(GetArmFeaturesEnumValue(&info.features, (ArmFeaturesEnum)~0x0));
}

TEST(CpuinfoArmTest, ODroidFromCpuInfo) {
  DisableHardwareCapabilities();
  auto& fs = GetEmptyFilesystem();
  fs.CreateFile("/proc/cpuinfo", R"(processor       : 0
model name      : ARMv7 Processor rev 3 (v71)
BogoMIPS        : 120.00
Features        : half thumb fastmult vfp edsp neon vfpv3 tls vfpv4 idiva idivt vfpd32 lpae
CPU implementer : 0x41
CPU architecture: 7
CPU variant     : 0x2
CPU part        : 0xc0f
CPU revision    : 3)");
  const auto info = GetArmInfo();
  EXPECT_EQ(info.implementer, 0x41);
  EXPECT_EQ(info.variant, 0x2);
  EXPECT_EQ(info.part, 0xc0f);
  EXPECT_EQ(info.revision, 3);
  EXPECT_EQ(info.architecture, 7);

  EXPECT_FALSE(info.features.swp);
  EXPECT_TRUE(info.features.half);
  EXPECT_TRUE(info.features.thumb);
  EXPECT_FALSE(info.features._26bit);
  EXPECT_TRUE(info.features.fastmult);
  EXPECT_FALSE(info.features.fpa);
  EXPECT_TRUE(info.features.vfp);
  EXPECT_TRUE(info.features.edsp);
  EXPECT_FALSE(info.features.java);
  EXPECT_FALSE(info.features.iwmmxt);
  EXPECT_FALSE(info.features.crunch);
  EXPECT_FALSE(info.features.thumbee);
  EXPECT_TRUE(info.features.neon);
  EXPECT_TRUE(info.features.vfpv3);
  EXPECT_FALSE(info.features.vfpv3d16);
  EXPECT_TRUE(info.features.tls);
  EXPECT_TRUE(info.features.vfpv4);
  EXPECT_TRUE(info.features.idiva);
  EXPECT_TRUE(info.features.idivt);
  EXPECT_TRUE(info.features.vfpd32);
  EXPECT_TRUE(info.features.lpae);
  EXPECT_FALSE(info.features.evtstrm);
  EXPECT_FALSE(info.features.aes);
  EXPECT_FALSE(info.features.pmull);
  EXPECT_FALSE(info.features.sha1);
  EXPECT_FALSE(info.features.sha2);
  EXPECT_FALSE(info.features.crc32);
}

// Linux test-case
TEST(CpuinfoArmTest, RaspberryPiZeroFromCpuInfo) {
  DisableHardwareCapabilities();
  auto& fs = GetEmptyFilesystem();
  fs.CreateFile("/proc/cpuinfo", R"(processor       : 0
model name      : ARMv6-compatible processor rev 7 (v6l)
BogoMIPS        : 697.95
Features        : half thumb fastmult vfp edsp java tls
CPU implementer : 0x41
CPU architecture: 7
CPU variant     : 0x0
CPU part        : 0xb76
CPU revision    : 7

Hardware        : BCM2835
Revision        : 9000c1
Serial          : 000000006cd946f3)");
  const auto info = GetArmInfo();
  EXPECT_EQ(info.implementer, 0x41);
  EXPECT_EQ(info.variant, 0x0);
  EXPECT_EQ(info.part, 0xb76);
  EXPECT_EQ(info.revision, 7);
  EXPECT_EQ(info.architecture, 6);

  EXPECT_FALSE(info.features.swp);
  EXPECT_TRUE(info.features.half);
  EXPECT_TRUE(info.features.thumb);
  EXPECT_FALSE(info.features._26bit);
  EXPECT_TRUE(info.features.fastmult);
  EXPECT_FALSE(info.features.fpa);
  EXPECT_TRUE(info.features.vfp);
  EXPECT_TRUE(info.features.edsp);
  EXPECT_TRUE(info.features.java);
  EXPECT_FALSE(info.features.iwmmxt);
  EXPECT_FALSE(info.features.crunch);
  EXPECT_FALSE(info.features.thumbee);
  EXPECT_FALSE(info.features.neon);
  EXPECT_FALSE(info.features.vfpv3);
  EXPECT_FALSE(info.features.vfpv3d16);
  EXPECT_TRUE(info.features.tls);
  EXPECT_FALSE(info.features.vfpv4);
  EXPECT_FALSE(info.features.idiva);
  EXPECT_FALSE(info.features.idivt);
  EXPECT_FALSE(info.features.vfpd32);
  EXPECT_FALSE(info.features.lpae);
  EXPECT_FALSE(info.features.evtstrm);
  EXPECT_FALSE(info.features.aes);
  EXPECT_FALSE(info.features.pmull);
  EXPECT_FALSE(info.features.sha1);
  EXPECT_FALSE(info.features.sha2);
  EXPECT_FALSE(info.features.crc32);
}

TEST(CpuinfoArmTest, MarvellArmadaFromCpuInfo) {
  DisableHardwareCapabilities();
  auto& fs = GetEmptyFilesystem();
  fs.CreateFile("/proc/cpuinfo", R"(processor       : 0
model name      : ARMv7 Processor rev 1 (v7l)
BogoMIPS        : 50.00
Features        : half thumb fastmult vfp edsp neon vfpv3 tls vfpd32
CPU implementer : 0x41
CPU architecture: 7
CPU variant     : 0x4
CPU part        : 0xc09
CPU revision    : 1

processor       : 1
model name      : ARMv7 Processor rev 1 (v7l)
BogoMIPS        : 50.00
Features        : half thumb fastmult vfp edsp neon vfpv3 tls vfpd32
CPU implementer : 0x41
CPU architecture: 7
CPU variant     : 0x4
CPU part        : 0xc09
CPU revision    : 1

Hardware        : Marvell Armada 380/385 (Device Tree)
Revision        : 0000
Serial          : 0000000000000000)");
  const auto info = GetArmInfo();
  EXPECT_EQ(info.implementer, 0x41);
  EXPECT_EQ(info.variant, 0x4);
  EXPECT_EQ(info.part, 0xc09);
  EXPECT_EQ(info.revision, 1);
  EXPECT_EQ(info.architecture, 7);

  EXPECT_FALSE(info.features.swp);
  EXPECT_TRUE(info.features.half);
  EXPECT_TRUE(info.features.thumb);
  EXPECT_FALSE(info.features._26bit);
  EXPECT_TRUE(info.features.fastmult);
  EXPECT_FALSE(info.features.fpa);
  EXPECT_TRUE(info.features.vfp);
  EXPECT_TRUE(info.features.edsp);
  EXPECT_FALSE(info.features.java);
  EXPECT_FALSE(info.features.iwmmxt);
  EXPECT_FALSE(info.features.crunch);
  EXPECT_FALSE(info.features.thumbee);
  EXPECT_TRUE(info.features.neon);
  EXPECT_TRUE(info.features.vfpv3);
  EXPECT_FALSE(info.features.vfpv3d16);
  EXPECT_TRUE(info.features.tls);
  EXPECT_FALSE(info.features.vfpv4);
  EXPECT_FALSE(info.features.idiva);
  EXPECT_FALSE(info.features.idivt);
  EXPECT_TRUE(info.features.vfpd32);
  EXPECT_FALSE(info.features.lpae);
  EXPECT_FALSE(info.features.evtstrm);
  EXPECT_FALSE(info.features.aes);
  EXPECT_FALSE(info.features.pmull);
  EXPECT_FALSE(info.features.sha1);
  EXPECT_FALSE(info.features.sha2);
  EXPECT_FALSE(info.features.crc32);
}

// Android test-case
// http://code.google.com/p/android/issues/detail?id=10812
TEST(CpuinfoArmTest, InvalidArmv7) {
  DisableHardwareCapabilities();
  auto& fs = GetEmptyFilesystem();
  fs.CreateFile("/proc/cpuinfo",
                R"(Processor       : ARMv6-compatible processor rev 6 (v6l)
BogoMIPS        : 199.47
Features        : swp half thumb fastmult vfp edsp java
CPU implementer : 0x41
CPU architecture: 7
CPU variant     : 0x0
CPU part        : 0xb76
CPU revision    : 6

Hardware        : SPICA
Revision        : 0020
Serial          : 33323613546d00ec )");
  const auto info = GetArmInfo();
  EXPECT_EQ(info.architecture, 6);

  EXPECT_TRUE(info.features.swp);
  EXPECT_TRUE(info.features.half);
  EXPECT_TRUE(info.features.thumb);
  EXPECT_FALSE(info.features._26bit);
  EXPECT_TRUE(info.features.fastmult);
  EXPECT_FALSE(info.features.fpa);
  EXPECT_TRUE(info.features.vfp);
  EXPECT_TRUE(info.features.edsp);
  EXPECT_TRUE(info.features.java);
  EXPECT_FALSE(info.features.iwmmxt);
  EXPECT_FALSE(info.features.crunch);
  EXPECT_FALSE(info.features.thumbee);
  EXPECT_FALSE(info.features.neon);
  EXPECT_FALSE(info.features.vfpv3);
  EXPECT_FALSE(info.features.vfpv3d16);
  EXPECT_FALSE(info.features.tls);
  EXPECT_FALSE(info.features.vfpv4);
  EXPECT_FALSE(info.features.idiva);
  EXPECT_FALSE(info.features.idivt);
  EXPECT_FALSE(info.features.vfpd32);
  EXPECT_FALSE(info.features.lpae);
  EXPECT_FALSE(info.features.evtstrm);
  EXPECT_FALSE(info.features.aes);
  EXPECT_FALSE(info.features.pmull);
  EXPECT_FALSE(info.features.sha1);
  EXPECT_FALSE(info.features.sha2);
  EXPECT_FALSE(info.features.crc32);
}

// Android test-case
// https://crbug.com/341598.
TEST(CpuinfoArmTest, InvalidNeon) {
  auto& fs = GetEmptyFilesystem();
  fs.CreateFile("/proc/cpuinfo",
                R"(Processor: ARMv7 Processory rev 0 (v71)
processor: 0
BogoMIPS: 13.50

Processor: 1
BogoMIPS: 13.50

Features: swp half thumb fastmult vfp edsp neon vfpv3 tls vfpv4 idiva idivt
CPU implementer : 0x51
CPU architecture: 7
CPU variant: 0x1
CPU part: 0x04d
CPU revision: 0

Hardware: SAMSUNG M2
Revision: 0010
Serial: 00001e030000354e)");
  const auto info = GetArmInfo();
  EXPECT_TRUE(info.features.swp);
  EXPECT_FALSE(info.features.neon);
}

// The Nexus 4 (Qualcomm Krait) kernel configuration forgets to report IDIV
// support.
TEST(CpuinfoArmTest, Nexus4_0x510006f2) {
  DisableHardwareCapabilities();
  auto& fs = GetEmptyFilesystem();
  fs.CreateFile("/proc/cpuinfo",
                R"(CPU implementer	: 0x51
CPU architecture: 7
CPU variant	: 0x0
CPU part	: 0x6f
CPU revision	: 2)");
  const auto info = GetArmInfo();
  EXPECT_TRUE(info.features.idiva);
  EXPECT_TRUE(info.features.idivt);

  EXPECT_EQ(GetArmCpuId(&info), 0x510006f2);
}

// The Nexus 4 (Qualcomm Krait) kernel configuration forgets to report IDIV
// support.
TEST(CpuinfoArmTest, Nexus4_0x510006f3) {
  DisableHardwareCapabilities();
  auto& fs = GetEmptyFilesystem();
  fs.CreateFile("/proc/cpuinfo",
                R"(CPU implementer	: 0x51
CPU architecture: 7
CPU variant	: 0x0
CPU part	: 0x6f
CPU revision	: 3)");
  const auto info = GetArmInfo();
  EXPECT_TRUE(info.features.idiva);
  EXPECT_TRUE(info.features.idivt);

  EXPECT_EQ(GetArmCpuId(&info), 0x510006f3);
}

// The emulator-specific Android 4.2 kernel fails to report support for the
// 32-bit ARM IDIV instruction. Technically, this is a feature of the virtual
// CPU implemented by the emulator.
TEST(CpuinfoArmTest, EmulatorSpecificIdiv) {
  DisableHardwareCapabilities();
  auto& fs = GetEmptyFilesystem();
  fs.CreateFile("/proc/cpuinfo",
                R"(Processor	: ARMv7 Processor rev 0 (v7l)
BogoMIPS	: 629.14
Features	: swp half thumb fastmult vfp edsp neon vfpv3
CPU implementer	: 0x41
CPU architecture: 7
CPU variant	: 0x0
CPU part	: 0xc08
CPU revision	: 0

Hardware	: Goldfish
Revision	: 0000
Serial		: 0000000000000000)");
  const auto info = GetArmInfo();
  EXPECT_TRUE(info.features.idiva);
}

}  // namespace
}  // namespace cpu_features
