blob: 86eae5e2ef1c15ea35379526d233fb46845603ce [file] [log] [blame]
// Copyright 2021 The Chromium OS 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 <memory>
#include <base/files/file.h>
#include <base/files/file_path.h>
#include <base/memory/ref_counted.h>
#include <base/files/scoped_temp_dir.h>
#include <base/sys_byteorder.h>
#include <gtest/gtest.h>
#include "hps/hal/fake_dev.h"
#include "hps/hps.h"
#include "hps/hps_impl.h"
#include "hps/hps_metrics.h"
#include "hps/hps_reg.h"
#include "hps/utils.h"
#include "metrics/metrics_library_mock.h"
using ::testing::_;
namespace {
class HPSTest : public testing::Test {
protected:
virtual void SetUp() {
fake_ = hps::FakeDev::Create();
hps_ = std::make_unique<hps::HPS_impl>(fake_->CreateDevInterface());
hps_->SetMetricsLibraryForTesting(std::make_unique<MetricsLibraryMock>());
}
void CreateBlob(const base::FilePath& file, int len) {
base::File f(file, base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE);
ASSERT_TRUE(f.IsValid());
f.SetLength(len);
}
MetricsLibraryMock* GetMetricsLibraryMock() {
return static_cast<MetricsLibraryMock*>(
hps_->metrics_library_for_testing());
}
scoped_refptr<hps::FakeDev> fake_;
std::unique_ptr<hps::HPS_impl> hps_;
};
class MockDownloadObserver {
public:
MOCK_METHOD(void,
OnProgress,
(const base::FilePath&, uint64_t, uint64_t, base::TimeDelta));
};
/*
* Check for a magic number.
*/
TEST_F(HPSTest, MagicNumber) {
EXPECT_EQ(hps_->Device()->ReadReg(hps::HpsReg::kMagic), hps::kHpsMagic);
}
/*
* Check that features can be enabled/disabled, and
* results are returned only when allowed.
*/
TEST_F(HPSTest, FeatureControl) {
hps::FeatureResult feature_result;
// No features enabled until module is ready.
EXPECT_FALSE(hps_->Enable(0));
EXPECT_FALSE(hps_->Enable(1));
feature_result = hps_->Result(0);
EXPECT_EQ(feature_result.valid, false);
// Set the module to be ready for features.
fake_->SkipBoot();
EXPECT_FALSE(hps_->Enable(hps::kFeatures));
EXPECT_FALSE(hps_->Disable(hps::kFeatures));
feature_result = hps_->Result(hps::kFeatures);
EXPECT_EQ(feature_result.valid, false);
ASSERT_TRUE(hps_->Enable(0));
ASSERT_TRUE(hps_->Enable(1));
// Check that enabled features can be disabled.
EXPECT_TRUE(hps_->Disable(0));
EXPECT_TRUE(hps_->Disable(1));
// Check that a result is returned if the feature is enabled.
const int result = -42;
fake_->SetF0Result(result);
feature_result = hps_->Result(0);
EXPECT_EQ(feature_result.valid, false);
ASSERT_TRUE(hps_->Enable(0));
feature_result = hps_->Result(0);
EXPECT_EQ(feature_result.valid, true);
EXPECT_EQ(feature_result.inference_result, result);
ASSERT_TRUE(hps_->Disable(0));
feature_result = hps_->Result(0);
EXPECT_EQ(feature_result.valid, false);
}
/*
* Download testing.
*/
TEST_F(HPSTest, Download) {
base::ScopedTempDir temp_dir;
ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
auto f = temp_dir.GetPath().Append("blob");
const int len = 1024;
CreateBlob(f, len);
// Download allowed to mcu flash in pre-booted state.
ASSERT_TRUE(hps_->Download(hps::HpsBank::kMcuFlash, f));
// Make sure the right amount was written.
EXPECT_EQ(fake_->GetBankLen(hps::HpsBank::kMcuFlash), len);
// Fail the memory write and confirm that the request fails.
// TODO(amcrae): Refactor to use enum directly.
fake_->Set(hps::FakeDev::Flags::kMemFail);
ASSERT_FALSE(hps_->Download(hps::HpsBank::kMcuFlash, f));
// No change to length.
EXPECT_EQ(fake_->GetBankLen(hps::HpsBank::kMcuFlash), len);
fake_->Clear(hps::FakeDev::Flags::kMemFail);
// Download not allowed to spi flash in pre-booted state.
EXPECT_FALSE(hps_->Download(hps::HpsBank::kSpiFlash, f));
fake_->SkipBoot();
// No downloads allowed when running.
EXPECT_FALSE(hps_->Download(hps::HpsBank::kMcuFlash, f));
EXPECT_FALSE(hps_->Download(hps::HpsBank::kSpiFlash, f));
}
/*
* Download testing with small block size
*/
TEST_F(HPSTest, DownloadSmallBlocks) {
base::ScopedTempDir temp_dir;
ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
auto f = temp_dir.GetPath().Append("blob");
const int len = 1024;
CreateBlob(f, len);
fake_->SetBlockSizeBytes(32);
// Download allowed to mcu flash in pre-booted state.
ASSERT_TRUE(hps_->Download(hps::HpsBank::kMcuFlash, f));
// Make sure the right amount was written.
EXPECT_EQ(fake_->GetBankLen(hps::HpsBank::kMcuFlash), len);
}
/*
* Observing download progress.
*/
TEST_F(HPSTest, DownloadProgressObserver) {
MockDownloadObserver observer;
base::ScopedTempDir temp_dir;
ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
auto f1 = temp_dir.GetPath().Append("blob1");
auto f2 = temp_dir.GetPath().Append("blob2");
const int kBlockSize = 256;
const int kLen1 = 1000;
const int kLen2 = 128;
CreateBlob(f1, kLen1);
CreateBlob(f2, kLen2);
fake_->SetBlockSizeBytes(kBlockSize);
hps_->SetDownloadObserver(base::BindRepeating(
&MockDownloadObserver::OnProgress, base::Unretained(&observer)));
{
// Download a file that is larger than the block size.
testing::InSequence s;
EXPECT_CALL(observer, OnProgress(f1, kLen1, kBlockSize, _));
EXPECT_CALL(observer, OnProgress(f1, kLen1, kBlockSize * 2, _));
EXPECT_CALL(observer, OnProgress(f1, kLen1, kBlockSize * 3, _));
EXPECT_CALL(observer, OnProgress(f1, kLen1, kLen1, _));
ASSERT_TRUE(hps_->Download(hps::HpsBank::kMcuFlash, f1));
}
{
// Download a file that is smaller than the block size.
testing::InSequence s;
EXPECT_CALL(observer, OnProgress(f2, kLen2, kLen2, _));
ASSERT_TRUE(hps_->Download(hps::HpsBank::kMcuFlash, f2));
}
}
/*
* Features cannot be enabled until after boot
*/
TEST_F(HPSTest, SkipBoot) {
// Make sure features can't be enabled.
ASSERT_FALSE(hps_->Enable(0));
// Put the fake straight into application stage.
fake_->SkipBoot();
// Inform the HPS handler that application stage is ready.
hps_->SkipBoot();
// Ensure that features can be enabled.
EXPECT_TRUE(hps_->Enable(0));
}
/*
* Test normal boot, where the versions match and
* the files are verified, so no flash update should occur.
*/
TEST_F(HPSTest, NormalBoot) {
base::ScopedTempDir temp_dir;
ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
// Create MCU and SPI flash filenames (but do not
// create the files themselves).
auto mcu = temp_dir.GetPath().Append("mcu");
auto spi = temp_dir.GetPath().Append("spi");
// Set the expected version
const uint32_t version = 0x01020304;
fake_->SetVersion(version);
// Set up the version and files.
hps_->Init(version, mcu, spi);
// Boot the module.
EXPECT_CALL(
*GetMetricsLibraryMock(),
SendEnumToUMA(hps::kHpsTurnOnResult,
static_cast<int>(hps::HpsTurnOnResult::kSuccess), _))
.Times(1);
ASSERT_TRUE(hps_->Boot());
// Ensure that features can be enabled.
EXPECT_TRUE(hps_->Enable(0));
EXPECT_EQ(fake_->GetBankLen(hps::HpsBank::kMcuFlash), 0);
EXPECT_EQ(fake_->GetBankLen(hps::HpsBank::kSpiFlash), 0);
}
/*
* Test that the MCU flash is updated when not verified.
*/
TEST_F(HPSTest, McuUpdate) {
base::ScopedTempDir temp_dir;
ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
// Create MCU and SPI flash filenames (but do not
// create the files themselves).
auto mcu = temp_dir.GetPath().Append("mcu");
auto spi = temp_dir.GetPath().Append("spi");
const int len = 1024;
CreateBlob(mcu, len);
// Set the expected version
const uint32_t version = 0x01020304;
fake_->SetVersion(version);
fake_->Set(hps::FakeDev::Flags::kApplNotVerified);
fake_->Set(hps::FakeDev::Flags::kResetApplVerification);
// Set up the version and files.
hps_->Init(version, mcu, spi);
// Boot the module.
EXPECT_CALL(
*GetMetricsLibraryMock(),
SendEnumToUMA(hps::kHpsTurnOnResult,
static_cast<int>(hps::HpsTurnOnResult::kMcuNotVerified), _))
.Times(1);
EXPECT_CALL(
*GetMetricsLibraryMock(),
SendEnumToUMA(hps::kHpsTurnOnResult,
static_cast<int>(hps::HpsTurnOnResult::kSuccess), _))
.Times(1);
ASSERT_TRUE(hps_->Boot());
// Check that MCU was downloaded.
EXPECT_EQ(fake_->GetBankLen(hps::HpsBank::kMcuFlash), len);
EXPECT_EQ(fake_->GetBankLen(hps::HpsBank::kSpiFlash), 0);
}
/*
* Test that the SPI flash is updated when not verified.
*/
TEST_F(HPSTest, SpiUpdate) {
base::ScopedTempDir temp_dir;
ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
// Create MCU and SPI flash filenames (but do not
// create the files themselves).
auto mcu = temp_dir.GetPath().Append("mcu");
auto spi = temp_dir.GetPath().Append("spi");
const int len = 1024;
CreateBlob(spi, len);
// Set the expected version
const uint32_t version = 0x01020304;
fake_->SetVersion(version);
fake_->Set(hps::FakeDev::Flags::kSpiNotVerified);
fake_->Set(hps::FakeDev::Flags::kResetSpiVerification);
// Set up the version and files.
hps_->Init(version, mcu, spi);
// Boot the module.
EXPECT_CALL(
*GetMetricsLibraryMock(),
SendEnumToUMA(hps::kHpsTurnOnResult,
static_cast<int>(hps::HpsTurnOnResult::kSpiNotVerified), _))
.Times(1);
EXPECT_CALL(
*GetMetricsLibraryMock(),
SendEnumToUMA(hps::kHpsTurnOnResult,
static_cast<int>(hps::HpsTurnOnResult::kSuccess), _))
.Times(1);
ASSERT_TRUE(hps_->Boot());
// Check that SPI was downloaded.
EXPECT_EQ(fake_->GetBankLen(hps::HpsBank::kMcuFlash), 0);
EXPECT_EQ(fake_->GetBankLen(hps::HpsBank::kSpiFlash), len);
}
/*
* Test that the both SPI and MCU are updated
* when not verified.
*/
TEST_F(HPSTest, BothUpdate) {
base::ScopedTempDir temp_dir;
ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
// Create MCU and SPI flash filenames (but do not
// create the files themselves).
auto mcu = temp_dir.GetPath().Append("mcu");
auto spi = temp_dir.GetPath().Append("spi");
const int len = 1024;
CreateBlob(spi, len);
CreateBlob(mcu, len);
// Set the expected version
const uint32_t version = 0x01020304;
fake_->SetVersion(version);
fake_->Set(hps::FakeDev::Flags::kApplNotVerified);
fake_->Set(hps::FakeDev::Flags::kResetApplVerification);
fake_->Set(hps::FakeDev::Flags::kSpiNotVerified);
fake_->Set(hps::FakeDev::Flags::kResetSpiVerification);
// Set up the version and files.
hps_->Init(version, mcu, spi);
// Boot the module.
EXPECT_CALL(
*GetMetricsLibraryMock(),
SendEnumToUMA(hps::kHpsTurnOnResult,
static_cast<int>(hps::HpsTurnOnResult::kMcuNotVerified), _))
.Times(1);
EXPECT_CALL(
*GetMetricsLibraryMock(),
SendEnumToUMA(hps::kHpsTurnOnResult,
static_cast<int>(hps::HpsTurnOnResult::kSpiNotVerified), _))
.Times(1);
EXPECT_CALL(
*GetMetricsLibraryMock(),
SendEnumToUMA(hps::kHpsTurnOnResult,
static_cast<int>(hps::HpsTurnOnResult::kSuccess), _))
.Times(1);
ASSERT_TRUE(hps_->Boot());
// Check that both MCU and SPI blobs were updated.
EXPECT_EQ(fake_->GetBankLen(hps::HpsBank::kMcuFlash), len);
EXPECT_EQ(fake_->GetBankLen(hps::HpsBank::kSpiFlash), len);
}
/*
* Test that neither SPI nor MCU are updated
* when write protect is off even if not verified.
*/
TEST_F(HPSTest, WpOffUpdate) {
base::ScopedTempDir temp_dir;
ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
// Create MCU and SPI flash filenames (but do not
// create the files themselves).
auto mcu = temp_dir.GetPath().Append("mcu");
auto spi = temp_dir.GetPath().Append("spi");
const int len = 1024;
CreateBlob(spi, len);
CreateBlob(mcu, len);
// This is the reported version when not verified
const uint32_t version = 0xFFFFFFFF;
fake_->Set(hps::FakeDev::Flags::kApplNotVerified);
fake_->Set(hps::FakeDev::Flags::kSpiNotVerified);
fake_->Set(hps::FakeDev::Flags::kWpOff);
// Set up the version and files.
hps_->Init(version, mcu, spi);
// Boot the module.
EXPECT_CALL(
*GetMetricsLibraryMock(),
SendEnumToUMA(hps::kHpsTurnOnResult,
static_cast<int>(hps::HpsTurnOnResult::kSuccess), _))
.Times(1);
ASSERT_TRUE(hps_->Boot());
// Check that neither MCU nor SPI blobs were updated.
EXPECT_EQ(fake_->GetBankLen(hps::HpsBank::kMcuFlash), 0);
EXPECT_EQ(fake_->GetBankLen(hps::HpsBank::kSpiFlash), 0);
}
/*
* Verify that mismatching version will update both MCU and SPI
*/
TEST_F(HPSTest, VersionUpdate) {
base::ScopedTempDir temp_dir;
ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
// Create MCU and SPI flash filenames (but do not
// create the files themselves).
auto mcu = temp_dir.GetPath().Append("mcu");
auto spi = temp_dir.GetPath().Append("spi");
const int len = 1024;
CreateBlob(spi, len);
CreateBlob(mcu, len);
// Set the current version
const uint32_t version = 0x01020304;
fake_->SetVersion(version);
fake_->Set(hps::FakeDev::Flags::kSpiNotVerified);
fake_->Set(hps::FakeDev::Flags::kResetSpiVerification);
fake_->Set(hps::FakeDev::Flags::kIncrementVersion);
// Set up the version to be the next version.
hps_->Init(version + 1, mcu, spi);
// Boot the module.
EXPECT_CALL(
*GetMetricsLibraryMock(),
SendEnumToUMA(hps::kHpsTurnOnResult,
static_cast<int>(hps::HpsTurnOnResult::kMcuVersionMismatch),
_))
.Times(1);
EXPECT_CALL(
*GetMetricsLibraryMock(),
SendEnumToUMA(hps::kHpsTurnOnResult,
static_cast<int>(hps::HpsTurnOnResult::kSpiNotVerified), _))
.Times(1);
EXPECT_CALL(
*GetMetricsLibraryMock(),
SendEnumToUMA(hps::kHpsTurnOnResult,
static_cast<int>(hps::HpsTurnOnResult::kSuccess), _))
.Times(1);
ASSERT_TRUE(hps_->Boot());
// Check that both MCU and SPI were downloaded.
EXPECT_EQ(fake_->GetBankLen(hps::HpsBank::kMcuFlash), len);
EXPECT_EQ(fake_->GetBankLen(hps::HpsBank::kSpiFlash), len);
}
// Check ReadVersionFromFile reads the right endianness from the right index
TEST(ReadVersionFromFile, CorrectVersion) {
base::ScopedTempDir temp_dir;
ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
base::FilePath path = temp_dir.GetPath().Append("blob");
base::File file(path,
base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE);
ASSERT_TRUE(file.IsValid());
char data[hps::kVersionOffset] = {};
ASSERT_EQ(hps::kVersionOffset,
file.WriteAtCurrentPos(data, hps::kVersionOffset));
// use a big version that tests the endianness
const uint32_t actual_version = 0x01020304U;
const uint32_t actual_version_be = base::HostToNet32(actual_version);
ASSERT_EQ(
sizeof(actual_version_be),
file.WriteAtCurrentPos(reinterpret_cast<const char*>(&actual_version_be),
sizeof(actual_version_be)));
uint32_t version;
ASSERT_TRUE(hps::ReadVersionFromFile(path, &version));
EXPECT_EQ(version, actual_version);
}
// Test ReadVersionFromFile behaviour when File is invalid
TEST(ReadVersionFromFile, BadFile) {
base::ScopedTempDir temp_dir;
ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
base::FilePath path = temp_dir.GetPath().Append("blob");
// nonexistent file
uint32_t version;
EXPECT_FALSE(hps::ReadVersionFromFile(path, &version));
// empty file
base::File file(path,
base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE);
ASSERT_TRUE(file.IsValid());
EXPECT_FALSE(hps::ReadVersionFromFile(path, &version));
}
} // namespace