blob: be32f02c8c6c3b7f2d6ac360c94bea6db704aa45 [file] [log] [blame]
// Copyright 2024 The Fuchsia 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 <fidl/fuchsia.hardware.clock/cpp/wire_test_base.h>
#include <lib/ddk/platform-defs.h>
#include <lib/driver/testing/cpp/fixtures/gtest_fixture.h>
#include <fake-mmio-reg/fake-mmio-reg.h>
#include <gtest/gtest.h>
#include <sdk/lib/inspect/testing/cpp/inspect.h>
#include "src/devices/bus/testing/fake-pdev/fake-pdev.h"
#include "src/devices/cpu/drivers/aml-cpu/aml-cpu-driver.h"
#include "src/lib/testing/predicates/status.h"
namespace amlogic_cpu {
using CpuCtrlClient = fidl::WireSyncClient<fuchsia_hardware_cpu_ctrl::Device>;
#define MHZ(x) ((x) * 1000000)
constexpr uint32_t kPdArmA53 = 1;
const std::vector<amlogic_cpu::perf_domain_t> kPerfDomains = {
{.id = kPdArmA53, .core_count = 4, .relative_performance = 255, .name = "S905D2 ARM A53"},
};
const std::vector<amlogic_cpu::operating_point_t> kOperatingPointsMetadata = {
{.freq_hz = 100'000'000, .volt_uv = 731'000, .pd_id = kPdArmA53},
{.freq_hz = 250'000'000, .volt_uv = 731'000, .pd_id = kPdArmA53},
{.freq_hz = 500'000'000, .volt_uv = 731'000, .pd_id = kPdArmA53},
{.freq_hz = 667'000'000, .volt_uv = 731'000, .pd_id = kPdArmA53},
{.freq_hz = 1'000'000'000, .volt_uv = 731'000, .pd_id = kPdArmA53},
{.freq_hz = 1'200'000'000, .volt_uv = 731'000, .pd_id = kPdArmA53},
{.freq_hz = 1'398'000'000, .volt_uv = 761'000, .pd_id = kPdArmA53},
{.freq_hz = 1'512'000'000, .volt_uv = 791'000, .pd_id = kPdArmA53},
{.freq_hz = 1'608'000'000, .volt_uv = 831'000, .pd_id = kPdArmA53},
{.freq_hz = 1'704'000'000, .volt_uv = 861'000, .pd_id = kPdArmA53},
{.freq_hz = 1'896'000'000, .volt_uv = 1'022'000, .pd_id = kPdArmA53},
};
const std::vector<amlogic_cpu::perf_domain_t> kTestPerfDomains = {
{.id = kPdArmA53, .core_count = 1, .relative_performance = 0, .name = "testpd"},
};
const std::vector<operating_point_t> kTestOperatingPoints = {
{.freq_hz = MHZ(10), .volt_uv = 1500, .pd_id = kPdArmA53},
{.freq_hz = MHZ(9), .volt_uv = 1350, .pd_id = kPdArmA53},
{.freq_hz = MHZ(8), .volt_uv = 1200, .pd_id = kPdArmA53},
{.freq_hz = MHZ(7), .volt_uv = 1050, .pd_id = kPdArmA53},
{.freq_hz = MHZ(6), .volt_uv = 900, .pd_id = kPdArmA53},
{.freq_hz = MHZ(5), .volt_uv = 750, .pd_id = kPdArmA53},
{.freq_hz = MHZ(4), .volt_uv = 600, .pd_id = kPdArmA53},
{.freq_hz = MHZ(3), .volt_uv = 450, .pd_id = kPdArmA53},
{.freq_hz = MHZ(2), .volt_uv = 300, .pd_id = kPdArmA53},
{.freq_hz = MHZ(1), .volt_uv = 150, .pd_id = kPdArmA53},
};
class FakeMmio {
public:
FakeMmio() : mmio_(sizeof(uint32_t), kRegCount) {
mmio_[kCpuVersionOffset].SetReadCallback([]() { return kCpuVersion; });
}
fdf::MmioBuffer mmio() { return mmio_.GetMmioBuffer(); }
private:
static constexpr size_t kCpuVersionOffset = 0x220;
static constexpr size_t kRegCount = kCpuVersionOffset / sizeof(uint32_t) + 1;
constexpr static uint64_t kCpuVersion = 0x28200b02;
ddk_fake::FakeMmioRegRegion mmio_;
};
class TestClockDevice : public fidl::testing::WireTestBase<fuchsia_hardware_clock::Clock> {
public:
fuchsia_hardware_clock::Service::InstanceHandler GetInstanceHandler() {
return fuchsia_hardware_clock::Service::InstanceHandler({
.clock = binding_group_.CreateHandler(
this, fdf::Dispatcher::GetCurrent()->async_dispatcher(), fidl::kIgnoreBindingClosure),
});
}
void Enable(EnableCompleter::Sync& completer) override {
enabled_ = true;
completer.ReplySuccess();
}
void Disable(DisableCompleter::Sync& completer) override {
enabled_ = false;
completer.ReplySuccess();
}
void IsEnabled(IsEnabledCompleter::Sync& completer) override { completer.ReplySuccess(enabled_); }
void SetRate(SetRateRequestView request, SetRateCompleter::Sync& completer) override {
rate_.emplace(request->hz);
completer.ReplySuccess();
}
void NotImplemented_(const std::string& name, ::fidl::CompleterBase& completer) final {
completer.Close(ZX_ERR_NOT_SUPPORTED);
}
fidl::ClientEnd<fuchsia_hardware_clock::Clock> Connect() {
zx::result endpoints = fidl::CreateEndpoints<fuchsia_hardware_clock::Clock>();
EXPECT_OK(endpoints.status_value());
binding_group_.AddBinding(fdf::Dispatcher::GetCurrent()->async_dispatcher(),
std::move(endpoints->server), this, fidl::kIgnoreBindingClosure);
return std::move(endpoints->client);
}
bool enabled() const { return enabled_; }
std::optional<uint32_t> rate() const { return rate_; }
private:
bool enabled_ = false;
std::optional<uint32_t> rate_;
fidl::ServerBindingGroup<fuchsia_hardware_clock::Clock> binding_group_;
};
class TestPowerDevice : public fidl::WireServer<fuchsia_hardware_power::Device> {
public:
fuchsia_hardware_power::Service::InstanceHandler GetInstanceHandler() {
return fuchsia_hardware_power::Service::InstanceHandler({
.device = binding_group_.CreateHandler(
this, fdf::Dispatcher::GetCurrent()->async_dispatcher(), fidl::kIgnoreBindingClosure),
});
}
void ConnectRequest(fidl::ServerEnd<fuchsia_hardware_power::Device> request) {
binding_group_.AddBinding(fdf::Dispatcher::GetCurrent()->async_dispatcher(), std::move(request),
this, fidl::kIgnoreBindingClosure);
}
fidl::ClientEnd<fuchsia_hardware_power::Device> Connect() {
auto endpoints = fidl::CreateEndpoints<fuchsia_hardware_power::Device>();
EXPECT_OK(endpoints.status_value());
ConnectRequest(std::move(endpoints->server));
return std::move(endpoints->client);
}
void RegisterPowerDomain(RegisterPowerDomainRequestView request,
RegisterPowerDomainCompleter::Sync& completer) override {
min_needed_voltage_ = request->min_needed_voltage;
max_supported_voltage_ = request->max_supported_voltage;
completer.ReplySuccess();
}
void UnregisterPowerDomain(UnregisterPowerDomainCompleter::Sync& completer) override {
completer.ReplySuccess();
}
void GetPowerDomainStatus(GetPowerDomainStatusCompleter::Sync& completer) override {
completer.ReplySuccess(fuchsia_hardware_power::wire::PowerDomainStatus::kEnabled);
}
void GetSupportedVoltageRange(GetSupportedVoltageRangeCompleter::Sync& completer) override {
completer.ReplySuccess(min_voltage_, max_voltage_);
}
void RequestVoltage(RequestVoltageRequestView request,
RequestVoltageCompleter::Sync& completer) override {
voltage_ = request->voltage;
completer.ReplySuccess(voltage_);
}
void GetCurrentVoltage(GetCurrentVoltageRequestView request,
GetCurrentVoltageCompleter::Sync& completer) override {
completer.ReplySuccess(voltage_);
}
void WritePmicCtrlReg(WritePmicCtrlRegRequestView request,
WritePmicCtrlRegCompleter::Sync& completer) override {
completer.ReplySuccess();
}
void ReadPmicCtrlReg(ReadPmicCtrlRegRequestView request,
ReadPmicCtrlRegCompleter::Sync& completer) override {
completer.ReplySuccess(1);
}
void SetSupportedVoltageRange(uint32_t min_voltage, uint32_t max_voltage) {
min_voltage_ = min_voltage;
max_voltage_ = max_voltage;
}
void SetVoltage(uint32_t voltage) { voltage_ = voltage; }
uint32_t voltage() const { return voltage_; }
uint32_t min_needed_voltage() const { return min_needed_voltage_; }
uint32_t max_supported_voltage() const { return max_supported_voltage_; }
private:
uint32_t voltage_ = 0;
uint32_t min_voltage_ = 0;
uint32_t max_voltage_ = 0;
uint32_t min_needed_voltage_ = 0;
uint32_t max_supported_voltage_ = 0;
fidl::ServerBindingGroup<fuchsia_hardware_power::Device> binding_group_;
};
class AmlCpuEnvironment : public fdf_testing::Environment {
public:
zx::result<> Serve(fdf::OutgoingDirectory& to_driver_vfs) {
auto dispatcher = fdf::Dispatcher::GetCurrent()->async_dispatcher();
device_server_.Init("pdev", "root");
EXPECT_EQ(ZX_OK, device_server_.Serve(dispatcher, &to_driver_vfs));
std::map<uint32_t, fake_pdev::Mmio> mmios;
mmios[0] = mmio_.mmio();
pdev_server_.SetConfig(fake_pdev::FakePDevFidl::Config{
.mmios = std::move(mmios),
.device_info =
pdev_device_info_t{
.pid = PDEV_PID_ASTRO,
},
});
EXPECT_OK(to_driver_vfs
.AddService<fuchsia_hardware_platform_device::Service>(
pdev_server_.GetInstanceHandler(dispatcher), "pdev")
.status_value());
power_server_.SetVoltage(0);
power_server_.SetSupportedVoltageRange(0, 0);
EXPECT_OK(to_driver_vfs
.AddService<fuchsia_hardware_power::Service>(power_server_.GetInstanceHandler(),
"power-01")
.status_value());
EXPECT_OK(to_driver_vfs
.AddService<fuchsia_hardware_clock::Service>(
clock_pll_div16_server_.GetInstanceHandler(), "clock-pll-div16-01")
.status_value());
EXPECT_OK(to_driver_vfs
.AddService<fuchsia_hardware_clock::Service>(
clock_cpu_div16_server_.GetInstanceHandler(), "clock-cpu-div16-01")
.status_value());
EXPECT_OK(to_driver_vfs
.AddService<fuchsia_hardware_clock::Service>(
clock_cpu_scaler_server_.GetInstanceHandler(), "clock-cpu-scaler-01")
.status_value());
return zx::ok();
}
compat::DeviceServer device_server_;
FakeMmio mmio_;
fake_pdev::FakePDevFidl pdev_server_;
TestPowerDevice power_server_;
TestClockDevice clock_pll_div16_server_;
TestClockDevice clock_cpu_div16_server_;
TestClockDevice clock_cpu_scaler_server_;
};
class AmlCpuBindingConfiguration final {
public:
static constexpr bool kDriverOnForeground = false;
static constexpr bool kAutoStartDriver = false;
static constexpr bool kAutoStopDriver = true;
using DriverType = AmlCpuDriver;
using EnvironmentType = AmlCpuEnvironment;
};
class AmlCpuTest : public fdf_testing::DriverTestFixture<AmlCpuBindingConfiguration> {
public:
void StartWithMetadata(const std::vector<perf_domain_t>& perf_domains,
const std::vector<operating_point_t>& op_points) {
// Notes on AmlCpu Initialization:
// + Should enable the CPU and PLL clocks.
// + Should initially assume that the device is in it's opp.
// + Should configure the device to it's highest opp.
const operating_point_t& slowest = op_points.front();
const operating_point_t& fastest = op_points.back();
RunInEnvironmentTypeContext([slowest, fastest](AmlCpuEnvironment& env) {
// The DUT should initialize.
env.power_server_.SetSupportedVoltageRange(slowest.volt_uv, fastest.volt_uv);
// The DUT scales up to the fastest available opp.
env.power_server_.SetVoltage(fastest.volt_uv);
});
RunInEnvironmentTypeContext([&perf_domains, &op_points](AmlCpuEnvironment& env) {
env.device_server_.AddMetadata(DEVICE_METADATA_AML_PERF_DOMAINS, perf_domains.data(),
perf_domains.size() * sizeof(perf_domain_t));
env.device_server_.AddMetadata(DEVICE_METADATA_AML_OP_POINTS, op_points.data(),
op_points.size() * sizeof(operating_point_t));
});
ASSERT_OK(StartDriver().status_value());
RunInEnvironmentTypeContext([slowest, fastest](AmlCpuEnvironment& env) {
ASSERT_EQ(env.power_server_.min_needed_voltage(), slowest.volt_uv);
ASSERT_EQ(env.power_server_.max_supported_voltage(), fastest.volt_uv);
});
RunInNodeContext([&perf_domains](fdf_testing::TestNode& node) {
ASSERT_EQ(node.children().size(), perf_domains.size());
});
}
void ConnectToCpuCtrl(const perf_domain_t& perf_domain) {
auto device = ConnectThroughDevfs<fuchsia_hardware_cpu_ctrl::Device>(perf_domain.name);
EXPECT_OK(device.status_value());
cpu_ctrl_.Bind(std::move(device.value()));
}
CpuCtrlClient cpu_ctrl_;
};
TEST_F(AmlCpuTest, TrivialBinding) { StartWithMetadata(kPerfDomains, kOperatingPointsMetadata); }
TEST_F(AmlCpuTest, UnorderedOperatingPoints) {
// AML CPU's bind hook expects that all operating points are strictly
// ordered and it should handle the situation where there are duplicate
// frequencies.
const std::vector<amlogic_cpu::operating_point_t> kOperatingPointsMetadata = {
{.freq_hz = MHZ(1), .volt_uv = 200'000, .pd_id = kPdArmA53},
{.freq_hz = MHZ(1), .volt_uv = 100'000, .pd_id = kPdArmA53},
{.freq_hz = MHZ(1), .volt_uv = 300'000, .pd_id = kPdArmA53},
};
StartWithMetadata(kPerfDomains, kOperatingPointsMetadata);
ConnectToCpuCtrl(kPerfDomains[0]);
auto out = cpu_ctrl_->SetCurrentOperatingPoint(0);
EXPECT_OK(out.status());
EXPECT_EQ(out->value()->out_opp, 0ul);
RunInEnvironmentTypeContext([](AmlCpuEnvironment& env) {
uint32_t voltage = env.power_server_.voltage();
EXPECT_EQ(voltage, 300'000u);
});
}
TEST_F(AmlCpuTest, TestGetOperatingPointInfo) {
StartWithMetadata(kTestPerfDomains, kTestOperatingPoints);
ConnectToCpuCtrl(kTestPerfDomains[0]);
auto opp_size = kTestOperatingPoints.size();
// Make sure that we can get information about all the supported opps.
for (size_t i = 0; i < opp_size; i++) {
const uint32_t opp = static_cast<uint32_t>(i);
auto oppInfo = cpu_ctrl_->GetOperatingPointInfo(opp);
// First, make sure there were no transport errors.
ASSERT_OK(oppInfo.status());
// Then make sure that the driver accepted the call.
ASSERT_FALSE(oppInfo->is_error());
// Then make sure that we're getting the expected frequency and voltage values.
EXPECT_EQ(oppInfo->value()->info.frequency_hz, kTestOperatingPoints[i].freq_hz);
EXPECT_EQ(oppInfo->value()->info.voltage_uv, kTestOperatingPoints[i].volt_uv);
}
// Make sure that we can't get any information about opps that don't
// exist.
for (size_t i = opp_size; i < opp_size + 10; i++) {
const uint32_t opp = static_cast<uint32_t>(i);
auto oppInfo = cpu_ctrl_->GetOperatingPointInfo(opp);
// Even if it's an unsupported opp, we still expect the transport to
// deliver the message successfully.
ASSERT_OK(oppInfo.status());
// Make sure that the driver returns an error, however.
EXPECT_TRUE(oppInfo->is_error());
}
}
TEST_F(AmlCpuTest, TestSetCurrentOperatingPoint) {
StartWithMetadata(kTestPerfDomains, kTestOperatingPoints);
ConnectToCpuCtrl(kTestPerfDomains[0]);
// Scale to the lowest opp.
const uint32_t min_opp_index = static_cast<uint32_t>(kTestOperatingPoints.size() - 1);
const operating_point_t& min_opp = kTestOperatingPoints[min_opp_index];
RunInEnvironmentTypeContext(
[&min_opp](AmlCpuEnvironment& env) { env.power_server_.SetVoltage(min_opp.volt_uv); });
auto min_result = cpu_ctrl_->SetCurrentOperatingPoint(min_opp_index);
EXPECT_OK(min_result.status());
EXPECT_TRUE(min_result->is_ok());
EXPECT_EQ(min_result->value()->out_opp, min_opp_index);
RunInEnvironmentTypeContext([&min_opp](AmlCpuEnvironment& env) {
auto rate = env.clock_cpu_scaler_server_.rate();
ASSERT_TRUE(rate.has_value());
ASSERT_EQ(rate.value(), min_opp.freq_hz);
});
// Check that we get the same value back from GetCurrentOperatingPoint
auto min_result_again = cpu_ctrl_->GetCurrentOperatingPoint();
EXPECT_OK(min_result_again.status());
EXPECT_EQ(min_result_again.value().out_opp, min_opp_index);
// Scale to the highest opp.
const uint32_t max_opp_index = 0;
const operating_point_t& max_opp = kTestOperatingPoints[max_opp_index];
RunInEnvironmentTypeContext(
[&max_opp](AmlCpuEnvironment& env) { env.power_server_.SetVoltage(max_opp.volt_uv); });
auto max_result = cpu_ctrl_->SetCurrentOperatingPoint(max_opp_index);
EXPECT_OK(max_result.status());
EXPECT_TRUE(max_result->is_ok());
EXPECT_EQ(max_result->value()->out_opp, max_opp_index);
RunInEnvironmentTypeContext([&max_opp](AmlCpuEnvironment& env) {
auto rate = env.clock_cpu_scaler_server_.rate();
ASSERT_TRUE(rate.has_value());
ASSERT_EQ(rate.value(), max_opp.freq_hz);
});
// Check that we get the same value back from GetCurrentOperatingPoint
auto max_result_again = cpu_ctrl_->GetCurrentOperatingPoint();
EXPECT_OK(max_result_again.status());
EXPECT_EQ(max_result_again.value().out_opp, max_opp_index);
// Set to the opp that we're already at and make sure that it's a no-op.
auto same_result = cpu_ctrl_->SetCurrentOperatingPoint(max_opp_index);
EXPECT_OK(same_result.status());
EXPECT_TRUE(same_result->is_ok());
EXPECT_EQ(same_result->value()->out_opp, max_opp_index);
// Check that we get the same value back from GetCurrentOperatingPoint
auto same_result_again = cpu_ctrl_->GetCurrentOperatingPoint();
EXPECT_OK(same_result_again.status());
EXPECT_EQ(same_result_again.value().out_opp, max_opp_index);
}
TEST_F(AmlCpuTest, TestCpuInfo) {
using namespace inspect::testing;
StartWithMetadata(kTestPerfDomains, kTestOperatingPoints);
RunInDriverContext([](AmlCpuDriver& driver) {
auto& dut = driver.performance_domains().front();
auto hierarchy = inspect::ReadFromVmo(dut->inspect_vmo());
EXPECT_TRUE(hierarchy.is_ok());
auto* cpu_info = hierarchy.value().GetByPath({"cpu_info_service"});
EXPECT_TRUE(cpu_info);
// cpu_major_revision : 40
EXPECT_THAT(*cpu_info, NodeMatches(AllOf(PropertyList(::testing::UnorderedElementsAre(
UintIs("cpu_major_revision", 40), UintIs("cpu_minor_revision", 11),
UintIs("cpu_package_id", 2))))));
});
}
TEST_F(AmlCpuTest, TestGetOperatingPointCount) {
StartWithMetadata(kTestPerfDomains, kTestOperatingPoints);
ConnectToCpuCtrl(kTestPerfDomains[0]);
auto resp = cpu_ctrl_->GetOperatingPointCount();
ASSERT_OK(resp.status());
EXPECT_EQ(resp.value()->count, kTestOperatingPoints.size());
}
TEST_F(AmlCpuTest, TestGetLogicalCoreCount) {
StartWithMetadata(kTestPerfDomains, kTestOperatingPoints);
ConnectToCpuCtrl(kTestPerfDomains[0]);
auto coreCountResp = cpu_ctrl_->GetNumLogicalCores();
ASSERT_OK(coreCountResp.status());
EXPECT_EQ(coreCountResp.value().count, kTestPerfDomains[0].core_count);
}
} // namespace amlogic_cpu