| // Copyright 2020 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 "aml-cpu.h" |
| |
| #include <fidl/fuchsia.hardware.thermal/cpp/wire.h> |
| #include <fuchsia/hardware/thermal/cpp/banjo.h> |
| #include <lib/async-loop/cpp/loop.h> |
| #include <lib/async-loop/default.h> |
| #include <lib/async_patterns/testing/cpp/dispatcher_bound.h> |
| #include <lib/component/outgoing/cpp/outgoing_directory.h> |
| #include <lib/device-protocol/pdev-fidl.h> |
| |
| #include <algorithm> |
| #include <memory> |
| #include <vector> |
| |
| #include <ddktl/device.h> |
| #include <ddktl/fidl.h> |
| #include <fake-mmio-reg/fake-mmio-reg.h> |
| #include <fbl/array.h> |
| #include <sdk/lib/inspect/testing/cpp/zxtest/inspect.h> |
| #include <soc/aml-common/aml-cpu-metadata.h> |
| #include <zxtest/zxtest.h> |
| |
| #include "src/devices/bus/testing/fake-pdev/fake-pdev.h" |
| #include "src/devices/testing/mock-ddk/mock-device.h" |
| |
| namespace amlogic_cpu { |
| |
| // Fake MMIO that exposes CPU version. |
| 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; |
| |
| // Note: FakeMmioReg's read callback returns a uint64_t, which is then cast to uint32_t when |
| // AmlCpu calls FakeMmioRegRegion::Read32. |
| constexpr static uint64_t kCpuVersion = 43; |
| |
| ddk_fake::FakeMmioRegRegion mmio_; |
| }; |
| |
| using CpuCtrlSyncClient = fidl::WireSyncClient<fuchsia_cpuctrl::Device>; |
| using ThermalSyncClient = fidl::WireSyncClient<fuchsia_thermal::Device>; |
| |
| constexpr size_t kBigClusterIdx = |
| static_cast<size_t>(fuchsia_thermal::wire::PowerDomain::kBigClusterPowerDomain); |
| constexpr size_t kLittleClusterIdx = |
| static_cast<size_t>(fuchsia_thermal::wire::PowerDomain::kLittleClusterPowerDomain); |
| |
| constexpr uint32_t kBigClusterCoreCount = 4; |
| constexpr uint32_t kLittleClusterCoreCount = 2; |
| |
| constexpr legacy_cluster_size_t kClusterSizeMetadata[] = { |
| { |
| .pd_id = kBigClusterIdx, |
| .core_count = kBigClusterCoreCount, |
| }, |
| { |
| .pd_id = kLittleClusterIdx, |
| .core_count = kLittleClusterCoreCount, |
| }, |
| }; |
| |
| constexpr size_t PowerDomainToIndex(fuchsia_thermal::wire::PowerDomain pd) { |
| switch (pd) { |
| case fuchsia_thermal::wire::PowerDomain::kLittleClusterPowerDomain: |
| return kLittleClusterIdx; |
| case fuchsia_thermal::wire::PowerDomain::kBigClusterPowerDomain: |
| return kBigClusterIdx; |
| } |
| __UNREACHABLE; |
| } |
| |
| const fuchsia_thermal::wire::OperatingPoint kFakeOperatingPoints = []() { |
| fuchsia_thermal::wire::OperatingPoint result; |
| |
| result.count = 3; |
| result.latency = 0; |
| result.opp[0].volt_uv = 1; |
| result.opp[0].freq_hz = 100; |
| result.opp[1].volt_uv = 2; |
| result.opp[1].freq_hz = 200; |
| result.opp[2].volt_uv = 3; |
| result.opp[2].freq_hz = 300; |
| |
| return result; |
| }(); |
| |
| const fuchsia_thermal::wire::ThermalDeviceInfo kDefaultDeviceInfo = []() { |
| fuchsia_thermal::wire::ThermalDeviceInfo result; |
| |
| result.active_cooling = false; |
| result.passive_cooling = false; |
| result.gpu_throttling = false; |
| result.num_trip_points = 0; |
| result.big_little = false; |
| result.critical_temp_celsius = 0; |
| |
| result.opps[kLittleClusterIdx].count = 0; |
| result.opps[kBigClusterIdx] = kFakeOperatingPoints; |
| |
| return result; |
| }(); |
| |
| class FakeAmlThermal : public fidl::WireServer<fuchsia_thermal::Device> { |
| public: |
| FakeAmlThermal() : active_operating_point_(0), device_info_(kDefaultDeviceInfo) {} |
| ~FakeAmlThermal() {} |
| |
| // Manage the Fake FIDL Message Loop |
| zx_status_t Init(fidl::ServerEnd<fuchsia_thermal::Device> remote); |
| |
| // Accessor |
| uint16_t ActiveOperatingPoint() const { return active_operating_point_; } |
| |
| void DdkRelease() {} |
| |
| void set_device_info(const fuchsia_thermal::wire::ThermalDeviceInfo& device_info) { |
| device_info_ = device_info; |
| } |
| |
| private: |
| // Implement Thermal FIDL Protocol. |
| void GetInfo(GetInfoCompleter::Sync& completer) override; |
| void GetDeviceInfo(GetDeviceInfoCompleter::Sync& completer) override; |
| void GetDvfsInfo(GetDvfsInfoRequestView request, GetDvfsInfoCompleter::Sync& completer) override; |
| void GetTemperatureCelsius(GetTemperatureCelsiusCompleter::Sync& completer) override; |
| void GetSensorName(GetSensorNameCompleter::Sync& completer) override {} |
| void GetStateChangeEvent(GetStateChangeEventCompleter::Sync& completer) override; |
| void GetStateChangePort(GetStateChangePortCompleter::Sync& completer) override; |
| void SetTripCelsius(SetTripCelsiusRequestView request, |
| SetTripCelsiusCompleter::Sync& completer) override; |
| void GetDvfsOperatingPoint(GetDvfsOperatingPointRequestView request, |
| GetDvfsOperatingPointCompleter::Sync& completer) override; |
| void SetDvfsOperatingPoint(SetDvfsOperatingPointRequestView request, |
| SetDvfsOperatingPointCompleter::Sync& completer) override; |
| void GetFanLevel(GetFanLevelCompleter::Sync& completer) override; |
| void SetFanLevel(SetFanLevelRequestView request, SetFanLevelCompleter::Sync& completer) override; |
| |
| uint16_t active_operating_point_; |
| async::Loop loop_{&kAsyncLoopConfigNeverAttachToThread}; |
| fuchsia_thermal::wire::ThermalDeviceInfo device_info_; |
| }; |
| |
| zx_status_t FakeAmlThermal::Init(fidl::ServerEnd<fuchsia_thermal::Device> request) { |
| loop_.StartThread("fake-aml-thermal"); |
| fidl::BindServer(loop_.dispatcher(), std::move(request), this, nullptr); |
| return ZX_OK; |
| } |
| |
| void FakeAmlThermal::GetInfo(GetInfoCompleter::Sync& completer) { |
| fuchsia_thermal::wire::ThermalInfo result; |
| |
| result.state = 0; |
| result.passive_temp_celsius = 0; |
| result.critical_temp_celsius = 0; |
| result.max_trip_count = 0; |
| |
| completer.Reply(ZX_OK, |
| fidl::ObjectView<fuchsia_thermal::wire::ThermalInfo>::FromExternal(&result)); |
| } |
| |
| void FakeAmlThermal::GetDeviceInfo(GetDeviceInfoCompleter::Sync& completer) { |
| fuchsia_thermal::wire::ThermalDeviceInfo result = device_info_; |
| completer.Reply( |
| ZX_OK, fidl::ObjectView<fuchsia_thermal::wire::ThermalDeviceInfo>::FromExternal(&result)); |
| } |
| |
| void FakeAmlThermal::GetDvfsInfo(GetDvfsInfoRequestView request, |
| GetDvfsInfoCompleter::Sync& completer) { |
| fuchsia_thermal::wire::ThermalDeviceInfo device_info = device_info_; |
| fuchsia_thermal::wire::OperatingPoint result = |
| device_info.opps[PowerDomainToIndex(request->power_domain)]; |
| completer.Reply(ZX_OK, |
| fidl::ObjectView<fuchsia_thermal::wire::OperatingPoint>::FromExternal(&result)); |
| } |
| |
| void FakeAmlThermal::GetTemperatureCelsius(GetTemperatureCelsiusCompleter::Sync& completer) { |
| completer.Reply(ZX_OK, 0.0); |
| } |
| |
| void FakeAmlThermal::GetStateChangeEvent(GetStateChangeEventCompleter::Sync& completer) { |
| zx::event invalid; |
| completer.Reply(ZX_ERR_NOT_SUPPORTED, std::move(invalid)); |
| } |
| |
| void FakeAmlThermal::GetStateChangePort(GetStateChangePortCompleter::Sync& completer) { |
| zx::port invalid; |
| completer.Reply(ZX_ERR_NOT_SUPPORTED, std::move(invalid)); |
| } |
| |
| void FakeAmlThermal::SetTripCelsius(SetTripCelsiusRequestView request, |
| SetTripCelsiusCompleter::Sync& completer) { |
| completer.Reply(ZX_ERR_NOT_SUPPORTED); |
| } |
| |
| void FakeAmlThermal::GetDvfsOperatingPoint(GetDvfsOperatingPointRequestView request, |
| GetDvfsOperatingPointCompleter::Sync& completer) { |
| if (request->power_domain == fuchsia_thermal::wire::PowerDomain::kLittleClusterPowerDomain) { |
| completer.Reply(ZX_ERR_NOT_SUPPORTED, 0); |
| return; |
| } |
| |
| completer.Reply(ZX_OK, active_operating_point_); |
| } |
| |
| void FakeAmlThermal::SetDvfsOperatingPoint(SetDvfsOperatingPointRequestView request, |
| SetDvfsOperatingPointCompleter::Sync& completer) { |
| if (request->power_domain == fuchsia_thermal::wire::PowerDomain::kLittleClusterPowerDomain) { |
| completer.Reply(ZX_ERR_NOT_SUPPORTED); |
| return; |
| } |
| |
| active_operating_point_ = request->op_idx; |
| completer.Reply(ZX_OK); |
| } |
| |
| void FakeAmlThermal::GetFanLevel(GetFanLevelCompleter::Sync& completer) { |
| completer.Reply(ZX_ERR_NOT_SUPPORTED, 0); |
| } |
| |
| void FakeAmlThermal::SetFanLevel(SetFanLevelRequestView request, |
| SetFanLevelCompleter::Sync& completer) { |
| completer.Reply(ZX_ERR_OUT_OF_RANGE); |
| } |
| |
| // Fake device that exposes the thermal banjo protocol. Upon calling Connect, a new instance of |
| // FakeAmlThermal is created to serve a client, at which point any previous FakeThermalAml |
| // instance is destroyed. |
| class FakeThermalDevice : public ddk::ThermalProtocol<FakeThermalDevice, ddk::base_protocol> { |
| public: |
| FakeThermalDevice() |
| : proto_({&thermal_protocol_ops_, this}), device_info_(kDefaultDeviceInfo), fidl_service_() {} |
| |
| zx_status_t ThermalConnect(zx::channel chan) { |
| fidl_service_ = std::make_unique<FakeAmlThermal>(); |
| fidl_service_->set_device_info(device_info_); |
| return fidl_service_->Init(fidl::ServerEnd<fuchsia_thermal::Device>(std::move(chan))); |
| } |
| |
| const thermal_protocol_t* proto() const { return &proto_; } |
| |
| void set_device_info(const fuchsia_thermal::wire::ThermalDeviceInfo& device_info) { |
| device_info_ = device_info; |
| } |
| |
| private: |
| thermal_protocol_t proto_; |
| fuchsia_thermal::wire::ThermalDeviceInfo device_info_; |
| std::unique_ptr<FakeAmlThermal> fidl_service_; |
| }; |
| |
| struct IncomingNamespace { |
| fake_pdev::FakePDevFidl pdev_server; |
| component::OutgoingDirectory outgoing{async_get_default_dispatcher()}; |
| }; |
| |
| // Fixture that supports tests of AmlCpu::Create. |
| class AmlCpuBindingTest : public zxtest::Test { |
| public: |
| AmlCpuBindingTest() { |
| root_ = MockDevice::FakeRootParent(); |
| |
| ASSERT_OK(incoming_loop_.StartThread("incoming-ns-thread")); |
| |
| fake_pdev::FakePDevFidl::Config config; |
| config.mmios[0] = mmio_.mmio(); |
| auto outgoing_endpoints = fidl::Endpoints<fuchsia_io::Directory>::Create(); |
| incoming_.SyncCall([config = std::move(config), server = std::move(outgoing_endpoints.server)]( |
| IncomingNamespace* infra) mutable { |
| infra->pdev_server.SetConfig(std::move(config)); |
| ASSERT_OK(infra->outgoing.AddService<fuchsia_hardware_platform_device::Service>( |
| infra->pdev_server.GetInstanceHandler())); |
| ASSERT_OK(infra->outgoing.Serve(std::move(server))); |
| }); |
| ASSERT_NO_FATAL_FAILURE(); |
| root_->AddFidlService(fuchsia_hardware_platform_device::Service::Name, |
| std::move(outgoing_endpoints.client), "pdev"); |
| |
| root_->AddProtocol(ZX_PROTOCOL_THERMAL, thermal_device_.proto()->ops, |
| thermal_device_.proto()->ctx, "thermal"); |
| |
| root_->SetMetadata(DEVICE_METADATA_CLUSTER_SIZE_LEGACY, &kClusterSizeMetadata, |
| sizeof(kClusterSizeMetadata)); |
| } |
| |
| protected: |
| std::shared_ptr<MockDevice> root_; |
| async::Loop incoming_loop_{&kAsyncLoopConfigNoAttachToCurrentThread}; |
| async_patterns::TestDispatcherBound<IncomingNamespace> incoming_{incoming_loop_.dispatcher(), |
| std::in_place}; |
| FakeMmio mmio_; |
| FakeThermalDevice thermal_device_; |
| }; |
| |
| TEST_F(AmlCpuBindingTest, OneDomain) { |
| ASSERT_OK(AmlCpu::Create(nullptr, root_.get())); |
| ASSERT_EQ(root_->child_count(), 1); |
| } |
| |
| TEST_F(AmlCpuBindingTest, TwoDomains) { |
| // Set up device info that defines two power domains. |
| thermal_device_.set_device_info([]() { |
| fuchsia_thermal::wire::ThermalDeviceInfo result; |
| |
| result.active_cooling = false; |
| result.passive_cooling = false; |
| result.gpu_throttling = false; |
| result.num_trip_points = 0; |
| result.big_little = true; |
| result.critical_temp_celsius = 0; |
| |
| result.opps[kLittleClusterIdx] = kFakeOperatingPoints; |
| result.opps[kBigClusterIdx] = kFakeOperatingPoints; |
| |
| return result; |
| }()); |
| |
| ASSERT_OK(AmlCpu::Create(nullptr, root_.get())); |
| ASSERT_EQ(root_->child_count(), 2); |
| |
| const auto& devices = root_->children(); |
| for (const auto& zxdev : devices) { |
| AmlCpu* device = zxdev->GetDeviceContext<AmlCpu>(); |
| const size_t idx = device->PowerDomainIndex(); |
| |
| // Find the cluster metadata that corresponds to this cluster index. |
| const auto& cluster_size_meta_itr = std::find_if( |
| std::begin(kClusterSizeMetadata), std::end(kClusterSizeMetadata), |
| [idx](const legacy_cluster_size_t& elem) -> bool { return idx == elem.pd_id; }); |
| |
| ASSERT_NE(cluster_size_meta_itr, std::end(kClusterSizeMetadata)); |
| ASSERT_EQ(cluster_size_meta_itr->core_count, device->ClusterCoreCount()); |
| } |
| } |
| |
| class AmlCpuTest : public AmlCpu { |
| public: |
| AmlCpuTest(ThermalSyncClient thermal) |
| : AmlCpu(nullptr, std::move(thermal), kBigClusterIdx, kBigClusterCoreCount) {} |
| |
| zx::vmo inspect_vmo() { return inspector_.DuplicateVmo(); } |
| }; |
| |
| using inspect::InspectTestHelper; |
| |
| class AmlCpuTestFixture : public InspectTestHelper, public zxtest::Test { |
| public: |
| explicit AmlCpuTestFixture() : loop_(&kAsyncLoopConfigNoAttachToCurrentThread) {} |
| void SetUp() override; |
| |
| protected: |
| FakeAmlThermal thermal_; |
| |
| async::Loop loop_; |
| std::unique_ptr<AmlCpuTest> dut_; |
| CpuCtrlSyncClient cpu_client_; |
| }; |
| |
| void AmlCpuTestFixture::SetUp() { |
| auto thermal_eps = fidl::Endpoints<fuchsia_thermal::Device>::Create(); |
| |
| ASSERT_OK(thermal_.Init(std::move(thermal_eps.server))); |
| ThermalSyncClient thermal_client = fidl::WireSyncClient(std::move(thermal_eps.client)); |
| |
| dut_ = std::make_unique<AmlCpuTest>(std::move(thermal_client)); |
| |
| auto cpu_eps = fidl::Endpoints<fuchsia_cpuctrl::Device>::Create(); |
| fidl::BindServer(loop_.dispatcher(), std::move(cpu_eps.server), dut_.get()); |
| loop_.StartThread("aml-cpu-legacy-test-thread"); |
| |
| cpu_client_ = CpuCtrlSyncClient(std::move(cpu_eps.client)); |
| } |
| |
| TEST_F(AmlCpuTestFixture, TestGetOperatingPointInfo) { |
| // Make sure that we can get information about all the supported opps. |
| for (uint32_t i = 0; i < kFakeOperatingPoints.count; i++) { |
| auto oppInfo = cpu_client_->GetOperatingPointInfo(i); |
| |
| // 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 accepted frequency and voltage values. |
| EXPECT_EQ(oppInfo->value()->info.frequency_hz, |
| kFakeOperatingPoints.opp[kFakeOperatingPoints.count - i - 1].freq_hz); |
| EXPECT_EQ(oppInfo->value()->info.voltage_uv, |
| kFakeOperatingPoints.opp[kFakeOperatingPoints.count - i - 1].volt_uv); |
| } |
| |
| // Make sure that we can't get any information about opps that don't exist. |
| for (uint32_t i = kFakeOperatingPoints.count; i < kFakeOperatingPoints.count + 10; i++) { |
| auto oppInfo = cpu_client_->GetOperatingPointInfo(i); |
| |
| // 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(AmlCpuTestFixture, TestSetCurrentOperatingPoint) { |
| // Make sure that we can drive the CPU to all of the supported operating |
| // points. |
| for (uint32_t i = 0; i < kFakeOperatingPoints.count; i++) { |
| uint32_t out_state = UINT32_MAX; |
| zx_status_t st = dut_->SetCurrentOperatingPointInternal(i, &out_state); |
| |
| // Make sure the call succeeded. |
| EXPECT_OK(st); |
| |
| // Make sure we could actually drive the device into the state that we |
| // expected. |
| EXPECT_EQ(out_state, i); |
| |
| // Make sure that the call was forwarded to the thermal driver. |
| const uint16_t kExpectedOperatingPoint = |
| static_cast<uint16_t>(kFakeOperatingPoints.count - i - 1); |
| EXPECT_EQ(kExpectedOperatingPoint, thermal_.ActiveOperatingPoint()); |
| } |
| |
| // Next make sure that we can't drive the CPU into any unsupported |
| // operating points. |
| for (uint32_t i = kFakeOperatingPoints.count; i < kFakeOperatingPoints.count + 10; i++) { |
| const uint16_t kInitialOperatingPoint = thermal_.ActiveOperatingPoint(); |
| uint32_t out_state = UINT32_MAX; |
| zx_status_t st = dut_->SetCurrentOperatingPointInternal(i, &out_state); |
| |
| // This is not a supported operating point. |
| EXPECT_NOT_OK(st); |
| |
| // Make sure we haven't meddled with `out_state` |
| EXPECT_EQ(out_state, UINT32_MAX); |
| |
| // Make sure we haven't meddled with the thermal driver's active |
| // operating point. |
| EXPECT_EQ(kInitialOperatingPoint, thermal_.ActiveOperatingPoint()); |
| } |
| } |
| |
| TEST_F(AmlCpuTestFixture, TestSetCpuInfo) { |
| uint32_t test_cpu_version = 0x28200b02; |
| dut_->SetCpuInfo(test_cpu_version); |
| ASSERT_NO_FATAL_FAILURE(ReadInspect(dut_->inspect_vmo())); |
| auto* cpu_info = hierarchy().GetByPath({"cpu_info_service"}); |
| ASSERT_TRUE(cpu_info); |
| |
| // cpu_major_revision : 40 |
| ASSERT_NO_FATAL_FAILURE( |
| CheckProperty(cpu_info->node(), "cpu_major_revision", inspect::UintPropertyValue(40))); |
| // cpu_minor_revision : 11 |
| ASSERT_NO_FATAL_FAILURE( |
| CheckProperty(cpu_info->node(), "cpu_minor_revision", inspect::UintPropertyValue(11))); |
| // cpu_package_id : 2 |
| ASSERT_NO_FATAL_FAILURE( |
| CheckProperty(cpu_info->node(), "cpu_package_id", inspect::UintPropertyValue(2))); |
| } |
| |
| TEST_F(AmlCpuTestFixture, TestGetOperatingPointCount) { |
| auto resp = cpu_client_->GetOperatingPointCount(); |
| |
| ASSERT_OK(resp.status()); |
| |
| EXPECT_EQ(resp.value()->count, kFakeOperatingPoints.count); |
| } |
| |
| TEST_F(AmlCpuTestFixture, TestGetNumLogicalCores) { |
| auto resp = cpu_client_->GetNumLogicalCores(); |
| |
| ASSERT_OK(resp.status()); |
| |
| EXPECT_EQ(resp.value().count, kBigClusterCoreCount); |
| } |
| |
| } // namespace amlogic_cpu |