blob: 4a3448ac763f8a1ddc6a6f5765b3b54796196920 [file]
// Copyright 2021 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 "src/devices/board/lib/acpi/manager.h"
#include <lib/ddk/binding.h>
#include <lib/ddk/debug.h>
#include <initializer_list>
#include <map>
#include <zxtest/zxtest.h>
#include "src/devices/board/lib/acpi/acpi.h"
#include "src/devices/board/lib/acpi/test/mock-acpi.h"
#include "src/devices/board/lib/acpi/test/mock-pci.h"
#include "src/devices/testing/mock-ddk/mock-device.h"
using acpi::test::Device;
static void ExpectProps(acpi::DeviceBuilder* b, std::vector<zx_device_prop_t> expected_dev,
std::vector<zx_device_str_prop_t> expected_str) {
auto dev_props = b->GetDevProps();
zxlogf(INFO, "size: %lu", dev_props.size());
size_t left = expected_dev.size();
for (auto& expected_prop : expected_dev) {
for (auto& actual : dev_props) {
if (actual.id == expected_prop.id) {
zxlogf(INFO, "%s 0x%x 0x%x vs 0x%x 0x%x", b->name(), actual.id, actual.value,
expected_prop.id, expected_prop.value);
ASSERT_EQ(actual.value, expected_prop.value);
left--;
}
}
}
ASSERT_EQ(left, 0);
auto& str_props = b->GetStrProps();
left = expected_str.size();
for (auto& expected_prop : expected_str) {
for (auto& actual : str_props) {
if (!strcmp(actual.key, expected_prop.key)) {
ASSERT_EQ(actual.property_value.value_type, expected_prop.property_value.value_type);
switch (actual.property_value.value_type) {
case ZX_DEVICE_PROPERTY_VALUE_INT:
ASSERT_EQ(actual.property_value.value.int_val,
expected_prop.property_value.value.int_val);
break;
case ZX_DEVICE_PROPERTY_VALUE_STRING:
ASSERT_STREQ(actual.property_value.value.str_val,
expected_prop.property_value.value.str_val);
break;
case ZX_DEVICE_PROPERTY_VALUE_BOOL:
ASSERT_EQ(actual.property_value.value.bool_val,
expected_prop.property_value.value.bool_val);
break;
case ZX_DEVICE_PROPERTY_VALUE_ENUM:
ASSERT_STREQ(actual.property_value.value.enum_val,
expected_prop.property_value.value.enum_val);
break;
default:
// This should never happen.
ASSERT_TRUE(false);
break;
}
left--;
}
}
}
ASSERT_EQ(left, 0);
}
class AcpiManagerTest : public zxtest::Test {
public:
AcpiManagerTest()
: mock_root_(MockDevice::FakeRootParent()), manager_(&acpi_, mock_root_.get()) {}
void SetUp() override { acpi_.SetDeviceRoot(std::make_unique<Device>("\\")); }
void InsertDeviceBelow(std::string path, std::unique_ptr<Device> d) {
Device* parent = acpi_.GetDeviceRoot()->FindByPath(path);
ASSERT_NE(parent, nullptr);
parent->AddChild(std::move(d));
}
void DiscoverConfigurePublish() {
auto ret = manager_.DiscoverDevices();
ASSERT_TRUE(ret.is_ok());
ret = manager_.ConfigureDiscoveredDevices();
ASSERT_TRUE(ret.is_ok());
ret = manager_.PublishDevices(mock_root_.get());
ASSERT_TRUE(ret.is_ok());
}
protected:
std::shared_ptr<MockDevice> mock_root_;
acpi::test::MockAcpi acpi_;
acpi::Manager manager_;
};
TEST_F(AcpiManagerTest, TestEnumerateEmptyTables) {
ASSERT_NO_FATAL_FAILURE(DiscoverConfigurePublish());
ASSERT_EQ(mock_root_->descendant_count(), 0);
}
TEST_F(AcpiManagerTest, TestEnumerateSystemBus) {
ASSERT_NO_FATAL_FAILURE(InsertDeviceBelow("\\", std::make_unique<Device>("_SB_")));
ASSERT_NO_FATAL_FAILURE(DiscoverConfigurePublish());
ASSERT_EQ(mock_root_->descendant_count(), 1);
}
TEST_F(AcpiManagerTest, TestDevicesOnPciBus) {
ASSERT_NO_FATAL_FAILURE(InsertDeviceBelow("\\", std::make_unique<Device>("_SB_")));
auto device = std::make_unique<Device>("PCI0");
device->SetHid("PNP0A08");
ASSERT_NO_FATAL_FAILURE(InsertDeviceBelow("\\_SB_", std::move(device)));
device = std::make_unique<Device>("TEST");
device->SetAdr(0x00010002);
ASSERT_NO_FATAL_FAILURE(InsertDeviceBelow("\\_SB_.PCI0", std::move(device)));
ASSERT_NO_FATAL_FAILURE(DiscoverConfigurePublish());
ASSERT_EQ(mock_root_->descendant_count(), 3);
Device* pci_bus = acpi_.GetDeviceRoot()->FindByPath("\\_SB_.PCI0");
// Check the PCI bus's type was set correctly.
acpi::DeviceBuilder* builder = manager_.LookupDevice(pci_bus);
ASSERT_EQ(builder->GetBusType(), acpi::BusType::kPci);
ASSERT_TRUE(builder->HasBusId());
ASSERT_EQ(builder->GetBusId(), 0);
// Check that pci_init was called with the correct bus:device.function.
std::vector<pci_bdf_t> bdfs = acpi::test::GetAcpiBdfs();
ASSERT_EQ(bdfs.size(), 1);
ASSERT_EQ(bdfs[0].bus_id, 0);
ASSERT_EQ(bdfs[0].device_id, 1);
ASSERT_EQ(bdfs[0].function_id, 2);
}
TEST_F(AcpiManagerTest, TestDeviceOnPciBusWithNoAdr) {
ASSERT_NO_FATAL_FAILURE(InsertDeviceBelow("\\", std::make_unique<Device>("_SB_")));
auto device = std::make_unique<Device>("PCI0");
device->SetHid("PNP0A08");
ASSERT_NO_FATAL_FAILURE(InsertDeviceBelow("\\_SB_", std::move(device)));
device = std::make_unique<Device>("TEST");
ASSERT_NO_FATAL_FAILURE(InsertDeviceBelow("\\_SB_.PCI0", std::move(device)));
ASSERT_NO_FATAL_FAILURE(DiscoverConfigurePublish());
ASSERT_EQ(mock_root_->descendant_count(), 3);
ASSERT_TRUE(acpi::test::GetAcpiBdfs().empty());
}
TEST_F(AcpiManagerTest, TestDeviceWithHidCid) {
ASSERT_NO_FATAL_FAILURE(InsertDeviceBelow("\\", std::make_unique<Device>("_SB_")));
auto device = std::make_unique<Device>("TEST");
device->SetHid("GGGG0000");
device->SetCids({"AAAA1111"});
ASSERT_NO_FATAL_FAILURE(InsertDeviceBelow("\\_SB_", std::move(device)));
ASSERT_NO_FATAL_FAILURE(DiscoverConfigurePublish());
Device* test = acpi_.GetDeviceRoot()->FindByPath("\\_SB_.TEST");
// Check the properties were generated as expected.
acpi::DeviceBuilder* builder = manager_.LookupDevice(test);
ASSERT_NO_FATAL_FAILURE(
ExpectProps(builder, {},
{
zx_device_str_prop_t{.key = "fuchsia.acpi.hid",
.property_value = str_prop_str_val("GGGG0000")},
zx_device_str_prop_t{.key = "fuchsia.acpi.first_cid",
.property_value = str_prop_str_val("AAAA1111")},
}));
}
static constexpr acpi::Uuid kDevicePropertiesUuid =
acpi::Uuid::Create(0xdaffd814, 0x6eba, 0x4d8c, 0x8a91, 0xbc9bbf4aa301);
TEST_F(AcpiManagerTest, TestDeviceWithDeviceTreeHid) {
ASSERT_NO_FATAL_FAILURE(InsertDeviceBelow("\\", std::make_unique<Device>("_SB_")));
auto device = std::make_unique<Device>("TEST");
device->SetHid("PRP0001");
ACPI_OBJECT compatible[2] = {{
.String = {.Type = ACPI_TYPE_STRING,
.Length = sizeof("compatible") - 1,
.Pointer = const_cast<char*>("compatible")},
},
{
.String = {.Type = ACPI_TYPE_STRING,
.Length = sizeof("google,cr50") - 1,
.Pointer = const_cast<char*>("google,cr50")},
}};
device->AddDsd(kDevicePropertiesUuid, ACPI_OBJECT{.Package = {.Type = ACPI_TYPE_PACKAGE,
.Count = std::size(compatible),
.Elements = compatible}});
ASSERT_NO_FATAL_FAILURE(InsertDeviceBelow("\\_SB_", std::move(device)));
ASSERT_NO_FATAL_FAILURE(DiscoverConfigurePublish());
Device* test = acpi_.GetDeviceRoot()->FindByPath("\\_SB_.TEST");
// Check the properties were generated as expected.
acpi::DeviceBuilder* builder = manager_.LookupDevice(test);
ASSERT_NO_FATAL_FAILURE(
ExpectProps(builder, {},
{
zx_device_str_prop_t{.key = "fuchsia.acpi.first_cid",
.property_value = str_prop_str_val("google,cr50")},
}));
}
TEST_F(AcpiManagerTest, TestDeviceWithDeviceTreeCid) {
ASSERT_NO_FATAL_FAILURE(InsertDeviceBelow("\\", std::make_unique<Device>("_SB_")));
auto device = std::make_unique<Device>("TEST");
device->SetCids({"PRP0001"});
ACPI_OBJECT compatible[2] = {{
.String = {.Type = ACPI_TYPE_STRING,
.Length = sizeof("compatible") - 1,
.Pointer = const_cast<char*>("compatible")},
},
{
.String = {.Type = ACPI_TYPE_STRING,
.Length = sizeof("google,cr50") - 1,
.Pointer = const_cast<char*>("google,cr50")},
}};
device->AddDsd(kDevicePropertiesUuid, ACPI_OBJECT{.Package = {.Type = ACPI_TYPE_PACKAGE,
.Count = std::size(compatible),
.Elements = compatible}});
ASSERT_NO_FATAL_FAILURE(InsertDeviceBelow("\\_SB_", std::move(device)));
ASSERT_NO_FATAL_FAILURE(DiscoverConfigurePublish());
Device* test = acpi_.GetDeviceRoot()->FindByPath("\\_SB_.TEST");
// Check the properties were generated as expected.
acpi::DeviceBuilder* builder = manager_.LookupDevice(test);
ASSERT_NO_FATAL_FAILURE(
ExpectProps(builder, {},
{
zx_device_str_prop_t{.key = "fuchsia.acpi.first_cid",
.property_value = str_prop_str_val("google,cr50")},
}));
}
TEST_F(AcpiManagerTest, TestSpiDevice) {
ASSERT_NO_FATAL_FAILURE(InsertDeviceBelow("\\", std::make_unique<Device>("_SB_")));
ASSERT_NO_FATAL_FAILURE(InsertDeviceBelow("\\_SB_", std::make_unique<Device>("SPI0")));
auto device = std::make_unique<Device>("S002");
ACPI_RESOURCE spi_resource = {
.Type = ACPI_RESOURCE_TYPE_SERIAL_BUS,
.Data =
{
.SpiSerialBus =
{
.Type = ACPI_RESOURCE_SERIAL_TYPE_SPI,
.SlaveMode = ACPI_CONTROLLER_INITIATED,
.ResourceSource =
{
.Index = 0,
.StringLength = sizeof("\\_SB_.SPI0") - 1,
.StringPtr = const_cast<char*>("\\_SB_.SPI0"),
},
.DevicePolarity = ACPI_SPI_ACTIVE_LOW,
.DataBitLength = 8,
.ClockPhase = ACPI_SPI_FIRST_PHASE,
.ClockPolarity = ACPI_SPI_START_HIGH,
.DeviceSelection = 2,
},
},
};
device->AddResource(spi_resource);
ASSERT_NO_FATAL_FAILURE(InsertDeviceBelow("\\_SB_.SPI0", std::move(device)));
ASSERT_NO_FATAL_FAILURE(DiscoverConfigurePublish());
Device* test = acpi_.GetDeviceRoot()->FindByPath("\\_SB_.SPI0.S002");
acpi::DeviceBuilder* builder = manager_.LookupDevice(test);
ASSERT_NO_FATAL_FAILURE(
ExpectProps(builder,
{
zx_device_prop_t{.id = BIND_SPI_BUS_ID, .value = 0},
zx_device_prop_t{.id = BIND_SPI_CHIP_SELECT, .value = 2},
zx_device_prop_t{.id = BIND_ACPI_BUS_TYPE, .value = acpi::BusType::kSpi},
},
{}));
}
TEST_F(AcpiManagerTest, TestI2cDevice) {
ASSERT_NO_FATAL_FAILURE(InsertDeviceBelow("\\", std::make_unique<Device>("_SB_")));
ASSERT_NO_FATAL_FAILURE(InsertDeviceBelow("\\_SB_", std::make_unique<Device>("I2C0")));
auto device = std::make_unique<Device>("H084");
ACPI_RESOURCE i2c_resource = {
.Type = ACPI_RESOURCE_TYPE_SERIAL_BUS,
.Data =
{
.I2cSerialBus =
{
.Type = ACPI_RESOURCE_SERIAL_TYPE_I2C,
.SlaveMode = ACPI_CONTROLLER_INITIATED,
.ResourceSource =
{
.Index = 0,
.StringLength = sizeof("\\_SB_.I2C0") - 1,
.StringPtr = const_cast<char*>("\\_SB_.I2C0"),
},
.AccessMode = ACPI_I2C_7BIT_MODE,
.SlaveAddress = 0x84,
},
},
};
device->AddResource(i2c_resource);
ASSERT_NO_FATAL_FAILURE(InsertDeviceBelow("\\_SB_.I2C0", std::move(device)));
ASSERT_NO_FATAL_FAILURE(DiscoverConfigurePublish());
Device* test = acpi_.GetDeviceRoot()->FindByPath("\\_SB_.I2C0.H084");
acpi::DeviceBuilder* builder = manager_.LookupDevice(test);
ASSERT_NO_FATAL_FAILURE(
ExpectProps(builder,
{
zx_device_prop_t{.id = BIND_I2C_BUS_ID, .value = 0},
zx_device_prop_t{.id = BIND_I2C_ADDRESS, .value = 0x84},
zx_device_prop_t{.id = BIND_ACPI_BUS_TYPE, .value = acpi::BusType::kI2c},
},
{}));
}
TEST_F(AcpiManagerTest, TestMultipleI2cDevice) {
ASSERT_NO_FATAL_FAILURE(InsertDeviceBelow("\\", std::make_unique<Device>("_SB_")));
ASSERT_NO_FATAL_FAILURE(InsertDeviceBelow("\\_SB_", std::make_unique<Device>("I2C0")));
ASSERT_NO_FATAL_FAILURE(InsertDeviceBelow("\\_SB_", std::make_unique<Device>("I2C1")));
auto device = std::make_unique<Device>("H084");
ACPI_RESOURCE i2c_resource = {
.Type = ACPI_RESOURCE_TYPE_SERIAL_BUS,
.Data =
{
.I2cSerialBus =
{
.Type = ACPI_RESOURCE_SERIAL_TYPE_I2C,
.SlaveMode = ACPI_CONTROLLER_INITIATED,
.ResourceSource =
{
.Index = 0,
.StringLength = sizeof("\\_SB_.I2C0") - 1,
.StringPtr = const_cast<char*>("\\_SB_.I2C0"),
},
.AccessMode = ACPI_I2C_7BIT_MODE,
.SlaveAddress = 0x84,
},
},
};
device->AddResource(i2c_resource);
ASSERT_NO_FATAL_FAILURE(InsertDeviceBelow("\\_SB_.I2C0", std::move(device)));
i2c_resource.Data.I2cSerialBus.ResourceSource = {.Index = 0,
.StringLength = sizeof("\\_SB_.I2C1") - 1,
.StringPtr = const_cast<char*>("\\_SB_.I2C1")};
i2c_resource.Data.I2cSerialBus.SlaveAddress = 0x72;
device = std::make_unique<Device>("H072");
device->AddResource(i2c_resource);
ASSERT_NO_FATAL_FAILURE(InsertDeviceBelow("\\_SB_.I2C1", std::move(device)));
ASSERT_NO_FATAL_FAILURE(DiscoverConfigurePublish());
Device* test = acpi_.GetDeviceRoot()->FindByPath("\\_SB_.I2C0.H084");
acpi::DeviceBuilder* builder = manager_.LookupDevice(test);
ASSERT_NO_FATAL_FAILURE(
ExpectProps(builder,
{
zx_device_prop_t{.id = BIND_I2C_BUS_ID, .value = 0},
zx_device_prop_t{.id = BIND_I2C_ADDRESS, .value = 0x84},
zx_device_prop_t{.id = BIND_ACPI_BUS_TYPE, .value = acpi::BusType::kI2c},
},
{}));
test = acpi_.GetDeviceRoot()->FindByPath("\\_SB_.I2C1.H072");
builder = manager_.LookupDevice(test);
ASSERT_NO_FATAL_FAILURE(
ExpectProps(builder,
{
zx_device_prop_t{.id = BIND_I2C_BUS_ID, .value = 1},
zx_device_prop_t{.id = BIND_I2C_ADDRESS, .value = 0x72},
zx_device_prop_t{.id = BIND_ACPI_BUS_TYPE, .value = acpi::BusType::kI2c},
},
{}));
}
TEST_F(AcpiManagerTest, TestDeviceNotPresentIsIgnored) {
auto device = std::make_unique<Device>("_SB_");
device->SetSta(0);
ASSERT_NO_FATAL_FAILURE(InsertDeviceBelow("\\", std::move(device)));
ASSERT_NO_FATAL_FAILURE(InsertDeviceBelow("\\_SB_", std::make_unique<Device>("I2C0")));
ASSERT_NO_FATAL_FAILURE(DiscoverConfigurePublish());
// No devices should have been published.
ASSERT_EQ(mock_root_->descendant_count(), 0);
}
TEST_F(AcpiManagerTest, TestDeviceNotPresentButFunctioning) {
auto device = std::make_unique<Device>("_SB_");
device->SetSta(ACPI_STA_DEVICE_FUNCTIONING);
ASSERT_NO_FATAL_FAILURE(InsertDeviceBelow("\\", std::move(device)));
ASSERT_NO_FATAL_FAILURE(InsertDeviceBelow("\\_SB_", std::make_unique<Device>("I2C0")));
ASSERT_NO_FATAL_FAILURE(DiscoverConfigurePublish());
// Both devices should have been enumerated.
// TODO(simonshields): check that _SB_ was added with the NON_BINDABLE flag set.
ASSERT_EQ(mock_root_->descendant_count(), 2);
}
TEST_F(AcpiManagerTest, TestDeviceNotEnabled) {
auto device = std::make_unique<Device>("_SB_");
device->SetSta(ACPI_STA_DEVICE_PRESENT);
ASSERT_NO_FATAL_FAILURE(InsertDeviceBelow("\\", std::move(device)));
ASSERT_NO_FATAL_FAILURE(InsertDeviceBelow("\\_SB_", std::make_unique<Device>("I2C0")));
// If the Manager incorrectly tries to access the device's resources, it will trigger an assert in
// MockAcpi.
ASSERT_NO_FATAL_FAILURE(DiscoverConfigurePublish());
}