blob: 0f2bd6b8cc7505db14f0de0b3af1287b7af1a914 [file] [log] [blame]
// 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 <lib/fake-bti/bti.h>
#include <lib/fake_ddk/fake_ddk.h>
#include <lib/sync/completion.h>
#include <ddktl/protocol/composite.h>
#include <fake-mmio-reg/fake-mmio-reg.h>
#include <mock-mmio-reg/mock-mmio-reg.h>
#include <mock/ddktl/protocol/gpio.h>
#include <soc/aml-s905d2/s905d2-hw.h>
#include <zxtest/zxtest.h>
#include "../audio-stream-in.h"
namespace audio::aml_g12 {
namespace audio_fidl = ::llcpp::fuchsia::hardware::audio;
class FakePDev : public ddk::PDevProtocol<FakePDev, ddk::base_protocol> {
public:
FakePDev() : proto_({&pdev_protocol_ops_, this}) {
regs_ = std::make_unique<ddk_fake::FakeMmioReg[]>(kRegCount);
mmio_ = std::make_unique<ddk_fake::FakeMmioRegRegion>(regs_.get(), sizeof(uint32_t), kRegCount);
}
const pdev_protocol_t* proto() const { return &proto_; }
zx_status_t PDevGetMmio(uint32_t index, pdev_mmio_t* out_mmio) {
EXPECT_LE(index, 1);
out_mmio->offset = reinterpret_cast<size_t>(this);
return ZX_OK;
}
zx_status_t PDevGetInterrupt(uint32_t index, uint32_t flags, zx::interrupt* out_irq) {
return ZX_ERR_NOT_SUPPORTED;
}
zx_status_t PDevGetBti(uint32_t index, zx::bti* out_bti) {
return fake_bti_create(out_bti->reset_and_get_address());
}
zx_status_t PDevGetSmc(uint32_t index, zx::resource* out_resource) {
return ZX_ERR_NOT_SUPPORTED;
}
zx_status_t PDevGetDeviceInfo(pdev_device_info_t* out_info) { return ZX_ERR_NOT_SUPPORTED; }
zx_status_t PDevGetBoardInfo(pdev_board_info_t* out_info) { return ZX_ERR_NOT_SUPPORTED; }
ddk::MmioBuffer mmio() { return ddk::MmioBuffer(mmio_->GetMmioBuffer()); }
ddk_fake::FakeMmioReg& reg(size_t ix) {
return regs_[ix >> 2]; // AML registers are in virtual address units.
}
private:
static constexpr size_t kRegCount =
S905D2_EE_AUDIO_LENGTH / sizeof(uint32_t); // in 32 bits chunks.
pdev_protocol_t proto_;
std::unique_ptr<ddk_fake::FakeMmioReg[]> regs_;
std::unique_ptr<ddk_fake::FakeMmioRegRegion> mmio_;
};
metadata::AmlPdmConfig GetDefaultMetadata() {
metadata::AmlPdmConfig metadata = {};
snprintf(metadata.manufacturer, sizeof(metadata.manufacturer), "Test");
snprintf(metadata.product_name, sizeof(metadata.product_name), "Test");
metadata.number_of_channels = 2;
metadata.version = metadata::AmlVersion::kS905D3G;
metadata.sysClockDivFactor = 4;
metadata.dClockDivFactor = 250;
return metadata;
}
class TestAudioStreamIn : public AudioStreamIn {
public:
explicit TestAudioStreamIn() : AudioStreamIn(fake_ddk::kFakeParent) {}
bool AllowNonContiguousRingBuffer() override { return true; }
};
audio_fidl::PcmFormat GetDefaultPcmFormat() {
audio_fidl::PcmFormat format;
format.number_of_channels = 2;
format.channels_to_use_bitmask = 0x03;
format.sample_format = audio_fidl::SampleFormat::PCM_SIGNED;
format.frame_rate = 48'000;
format.bytes_per_sample = 2;
format.valid_bits_per_sample = 16;
return format;
}
struct AudioStreamInTest : public zxtest::Test {
void SetUp() override {
static constexpr size_t kNumBindProtocols = 1;
fbl::Array<fake_ddk::ProtocolEntry> protocols(new fake_ddk::ProtocolEntry[kNumBindProtocols],
kNumBindProtocols);
protocols[0] = {ZX_PROTOCOL_PDEV, *reinterpret_cast<const fake_ddk::Protocol*>(pdev_.proto())};
tester_.SetProtocols(std::move(protocols));
}
void TestMasks(uint8_t number_of_channels, uint64_t channels_to_use_bitmask,
uint8_t channels_mask, uint8_t mute_mask) {
auto metadata = GetDefaultMetadata();
metadata.number_of_channels = number_of_channels;
tester_.SetMetadata(&metadata, sizeof(metadata));
int step = 0; // Track of the expected sequence of reads and writes.
pdev_.reg(0x000).SetReadCallback([]() -> uint32_t { return 0; });
pdev_.reg(0x000).SetWriteCallback([&step, &mute_mask](size_t value) {
if (step == 8) {
EXPECT_EQ(mute_mask << 20, value);
}
step++;
});
auto server = audio::SimpleAudioStream::Create<TestAudioStreamIn>();
ASSERT_NOT_NULL(server);
audio_fidl::Device::SyncClient client_wrap(std::move(tester_.FidlClient()));
audio_fidl::Device::ResultOf::GetChannel channel_wrap = client_wrap.GetChannel();
ASSERT_EQ(channel_wrap.status(), ZX_OK);
audio_fidl::StreamConfig::SyncClient client(std::move(channel_wrap->channel));
audio_fidl::PcmFormat pcm_format = GetDefaultPcmFormat();
pcm_format.channels_to_use_bitmask = channels_to_use_bitmask;
pcm_format.number_of_channels = number_of_channels;
fidl::aligned<audio_fidl::PcmFormat> aligned_pcm_format = std::move(pcm_format);
auto builder = audio_fidl::Format::UnownedBuilder();
builder.set_pcm_format(fidl::unowned_ptr(&aligned_pcm_format));
zx::channel local, remote;
ASSERT_OK(zx::channel::create(0, &local, &remote));
client.CreateRingBuffer(builder.build(), std::move(remote));
// To make sure we have initialized in the server make a sync call
// (we know the server is single threaded, initialization is completed if received a reply).
auto props = audio_fidl::RingBuffer::Call::GetProperties(zx::unowned_channel(local));
ASSERT_OK(props.status());
server->DdkAsyncRemove();
EXPECT_TRUE(tester_.Ok());
server->DdkRelease();
EXPECT_EQ(step, 12);
}
void TestRingBufferSize(uint8_t number_of_channels, uint32_t frames_req,
uint32_t frames_expected) {
auto metadata = GetDefaultMetadata();
metadata.number_of_channels = number_of_channels;
tester_.SetMetadata(&metadata, sizeof(metadata));
auto stream = audio::SimpleAudioStream::Create<TestAudioStreamIn>();
audio_fidl::Device::SyncClient client_wrap(std::move(tester_.FidlClient()));
audio_fidl::Device::ResultOf::GetChannel ch = client_wrap.GetChannel();
ASSERT_EQ(ch.status(), ZX_OK);
audio_fidl::StreamConfig::SyncClient client(std::move(ch->channel));
zx::channel local, remote;
ASSERT_OK(zx::channel::create(0, &local, &remote));
fidl::aligned<audio_fidl::PcmFormat> pcm_format = GetDefaultPcmFormat();
pcm_format.value.number_of_channels = number_of_channels;
auto builder = audio_fidl::Format::UnownedBuilder();
builder.set_pcm_format(fidl::unowned_ptr(&pcm_format));
client.CreateRingBuffer(builder.build(), std::move(remote));
auto vmo = audio_fidl::RingBuffer::Call::GetVmo(zx::unowned_channel(local), frames_req, 0);
ASSERT_OK(vmo.status());
ASSERT_EQ(vmo.Unwrap()->result.response().num_frames, frames_expected);
stream->DdkAsyncRemove();
EXPECT_TRUE(tester_.Ok());
stream->DdkRelease();
}
FakePDev pdev_;
fake_ddk::Bind tester_;
};
TEST_F(AudioStreamInTest, ChannelsToUseBitmaskAllOn) {
TestMasks(/*channels*/ 2, /*channels_to_use_bitmask*/ 3, /*channels_mask*/ 3, /*mute_mask*/ 0);
}
TEST_F(AudioStreamInTest, ChannelsToUseBitmaskLeftOn) {
TestMasks(/*channels*/ 2, /*channels_to_use_bitmask*/ 1, /*channels_mask*/ 3, /*mute_mask*/ 2);
}
TEST_F(AudioStreamInTest, ChannelsToUseBitmaskRightOn) {
TestMasks(/*channels*/ 2, /*channels_to_use_bitmask*/ 2, /*channels_mask*/ 3, /*mute_mask*/ 1);
}
TEST_F(AudioStreamInTest, ChannelsToUseBitmaskMoreThanNeeded) {
TestMasks(/*channels*/ 2, /*channels_to_use_bitmask*/ 0xff, /*channels_mask*/ 3, /*mute_mask*/ 0);
}
// With 16 bits samples, frame size is 2 x number of channels bytes.
// Frames returned are rounded to HW buffer alignment (8 bytes) and frame size.
TEST_F(AudioStreamInTest, RingBufferSize1) {
TestRingBufferSize(2, 1, 2);
} // Rounded to HW buffer.
TEST_F(AudioStreamInTest, RingBufferSize2) {
TestRingBufferSize(2, 3, 4);
} // Rounded to HW buffer.
TEST_F(AudioStreamInTest, RingBufferSize3) { TestRingBufferSize(3, 1, 4); } // Rounded to both.
TEST_F(AudioStreamInTest, RingBufferSize4) { TestRingBufferSize(3, 3, 4); } // Rounded to both.
TEST_F(AudioStreamInTest, RingBufferSize5) {
TestRingBufferSize(8, 1, 1);
} // Rounded to frame size.
TEST_F(AudioStreamInTest, RingBufferSize6) {
TestRingBufferSize(8, 3, 3);
} // Rounded to frame size.
} // namespace audio::aml_g12
// Redefine PDevMakeMmioBufferWeak per the recommendation in pdev.h.
zx_status_t ddk::PDevMakeMmioBufferWeak(const pdev_mmio_t& pdev_mmio,
std::optional<MmioBuffer>* mmio, uint32_t cache_policy) {
auto* test_harness = reinterpret_cast<audio::aml_g12::FakePDev*>(pdev_mmio.offset);
mmio->emplace(test_harness->mmio());
return ZX_OK;
}