blob: 5139026ee9bbb2ac12dcbeb3777ae09c2310ea58 [file] [log] [blame]
// Copyright 2019 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 "sdio-controller-device.h"
#include <fbl/auto_lock.h>
#include <hw/sdio.h>
#include <lib/fake_ddk/fake_ddk.h>
#include <zxtest/zxtest.h>
#include "mock-sdmmc-device.h"
namespace sdmmc {
class Bind : public fake_ddk::Bind {
public:
int total_children() const { return total_children_; }
zx_status_t DeviceAdd(zx_driver_t* drv, zx_device_t* parent, device_add_args_t* args,
zx_device_t** out) override {
if (parent == fake_ddk::kFakeParent) {
*out = fake_ddk::kFakeDevice;
add_called_ = true;
} else if (parent == fake_ddk::kFakeDevice) {
*out = kFakeChild;
children_++;
total_children_++;
} else {
*out = kUnknownDevice;
bad_parent_ = false;
}
return ZX_OK;
}
zx_status_t DeviceRemove(zx_device_t* device) override {
if (device == fake_ddk::kFakeDevice) {
remove_called_ = true;
} else if (device == kFakeChild) {
// Check that all children are removed before the parent is removed.
if (!remove_called_) {
children_--;
}
} else {
bad_device_ = true;
}
return ZX_OK;
}
void Ok() {
EXPECT_EQ(children_, 0);
EXPECT_TRUE(add_called_);
EXPECT_TRUE(remove_called_);
EXPECT_FALSE(bad_parent_);
EXPECT_FALSE(bad_device_);
}
private:
zx_device_t* kFakeChild = reinterpret_cast<zx_device_t*>(0x1234);
zx_device_t* kUnknownDevice = reinterpret_cast<zx_device_t*>(0x5678);
int total_children_ = 0;
int children_ = 0;
bool bad_parent_ = false;
bool bad_device_ = false;
bool add_called_ = false;
bool remove_called_ = false;
};
class SdioControllerDeviceTest : public SdioControllerDevice {
public:
SdioControllerDeviceTest(MockSdmmcDevice* mock_sdmmc, const sdio_device_hw_info_t& hw_info)
: SdioControllerDevice(fake_ddk::kFakeParent, SdmmcDevice({}, {})),
mock_sdmmc_(mock_sdmmc) {
hw_info_ = hw_info;
}
void SetSdioFunctionInfo(uint8_t fn_idx, const SdioFunction& info) {
fbl::AutoLock lock(&lock_);
funcs_[fn_idx] = info;
}
auto& mock_SdioDoRwByte() { return mock_sdio_do_rw_byte_; }
void VerifyAll() { mock_sdio_do_rw_byte_.VerifyAndClear(); }
zx_status_t SdioDoRwByte(bool write, uint8_t fn_idx, uint32_t addr, uint8_t write_byte,
uint8_t* out_read_byte) override {
if (mock_sdio_do_rw_byte_.HasExpectations()) {
std::tuple<zx_status_t, uint8_t> ret =
mock_sdio_do_rw_byte_.Call(write, fn_idx, addr, write_byte);
if (out_read_byte != nullptr) {
*out_read_byte = std::get<1>(ret);
}
return std::get<0>(ret);
} else {
return SdioControllerDevice::SdioDoRwByte(write, fn_idx, addr, write_byte,
out_read_byte);
}
}
// Registers an interrupt with the SDIO controller for the given function. The interrupt is
// managed by this object.
zx_status_t RegisterInterrupt(uint8_t fn_idx) {
zx_status_t status = ZX_OK;
if (interrupts_[fn_idx].is_valid()) {
return status;
} else if (!port_.is_valid()) {
if ((status = zx::port::create(ZX_PORT_BIND_TO_INTERRUPT, &port_)) != ZX_OK) {
return status;
}
}
if ((status = SdioGetInBandIntr(fn_idx, &interrupts_[fn_idx])) != ZX_OK) {
return status;
}
return interrupts_[fn_idx].bind(port_, fn_idx, 0);
}
// Wait for count interrupts to be received for any combination of functions. Upon return the
// bits in mask represent the different functions which had interrupts triggered.
zx_status_t WaitForInterrupts(uint32_t count, uint8_t* mask) {
*mask = 0;
for (uint32_t i = 0; i < count; i++) {
zx_port_packet_t packet;
zx_status_t status = port_.wait(zx::time::infinite(), &packet);
if (status != ZX_OK) {
return status;
}
*mask |= static_cast<uint8_t>(1 << packet.key);
interrupts_[packet.key].ack();
}
return ZX_OK;
}
private:
SdmmcDevice& sdmmc() override { return *mock_sdmmc_; }
MockSdmmcDevice* mock_sdmmc_;
mock_function::MockFunction<std::tuple<zx_status_t, uint8_t>, bool, uint8_t, uint32_t, uint8_t>
mock_sdio_do_rw_byte_;
zx::port port_;
zx::interrupt interrupts_[SDIO_MAX_FUNCS];
};
TEST(SdioControllerDeviceTest, MultiplexInterrupts) {
MockSdmmcDevice mock_sdmmc({});
SdioControllerDeviceTest dut(&mock_sdmmc, {});
ASSERT_OK(dut.StartSdioIrqThread());
ASSERT_OK(dut.RegisterInterrupt(1));
ASSERT_OK(dut.RegisterInterrupt(2));
ASSERT_OK(dut.RegisterInterrupt(4));
ASSERT_OK(dut.RegisterInterrupt(7));
dut.mock_SdioDoRwByte()
.ExpectCall({ZX_OK, 0b0000'0010}, false, 0, SDIO_CIA_CCCR_INTx_INTR_PEN_ADDR, 0)
.ExpectCall({ZX_OK, 0b1111'1110}, false, 0, SDIO_CIA_CCCR_INTx_INTR_PEN_ADDR, 0)
.ExpectCall({ZX_OK, 0b1010'0010}, false, 0, SDIO_CIA_CCCR_INTx_INTR_PEN_ADDR, 0)
.ExpectCall({ZX_OK, 0b0011'0110}, false, 0, SDIO_CIA_CCCR_INTx_INTR_PEN_ADDR, 0);
uint8_t mask;
dut.InBandInterruptCallback();
EXPECT_OK(dut.WaitForInterrupts(1, &mask));
EXPECT_EQ(mask, 0b0000'0010);
dut.InBandInterruptCallback();
EXPECT_OK(dut.WaitForInterrupts(4, &mask));
EXPECT_EQ(mask, 0b1001'0110);
dut.InBandInterruptCallback();
EXPECT_OK(dut.WaitForInterrupts(2, &mask));
EXPECT_EQ(mask, 0b1000'0010);
dut.InBandInterruptCallback();
EXPECT_OK(dut.WaitForInterrupts(3, &mask));
EXPECT_EQ(mask, 0b0001'0110);
dut.StopSdioIrqThread();
dut.VerifyAll();
mock_sdmmc.VerifyAll();
}
TEST(SdioControllerDeviceTest, SdioDoRwTxn) {
MockSdmmcDevice mock_sdmmc({
.caps = 0,
.max_transfer_size = 16,
.max_transfer_size_non_dma = 16,
.prefs = 0
});
SdioControllerDeviceTest dut(&mock_sdmmc, {});
dut.SetSdioFunctionInfo(3, {
.hw_info = {},
.cur_blk_size = 8,
.enabled = true,
.intr_enabled = false
});
mock_sdmmc.mock_SdioIoRwExtended()
.ExpectCall(ZX_OK, 0, true, 3, 0xabcd0008, false, 1, 8, 16)
.ExpectCall(ZX_OK, 0, true, 3, 0xabcd0008, false, 1, 8, 24)
.ExpectCall(ZX_OK, 0, true, 3, 0xabcd0008, false, 1, 8, 32)
.ExpectCall(ZX_OK, 0, true, 3, 0xabcd0008, false, 1, 8, 40)
.ExpectCall(ZX_OK, 0, true, 3, 0xabcd0008, false, 1, 4, 48)
.ExpectCall(ZX_OK, 0, false, 3, 0x12340008, true, 1, 8, 16)
.ExpectCall(ZX_OK, 0, false, 3, 0x12340010, true, 1, 8, 24)
.ExpectCall(ZX_OK, 0, false, 3, 0x12340018, true, 1, 8, 32)
.ExpectCall(ZX_OK, 0, false, 3, 0x12340020, true, 1, 8, 40)
.ExpectCall(ZX_OK, 0, false, 3, 0x12340028, true, 1, 4, 48);
sdio_rw_txn_t txn = {
.addr = 0xabcd0008,
.data_size = 36,
.incr = false,
.write = true,
.use_dma = false,
.dma_vmo = ZX_HANDLE_INVALID,
.virt_buffer = nullptr,
.virt_size = 0,
.buf_offset = 16
};
EXPECT_OK(dut.SdioDoRwTxn(3, &txn));
txn = {
.addr = 0x12340008,
.data_size = 36,
.incr = true,
.write = false,
.use_dma = false,
.dma_vmo = ZX_HANDLE_INVALID,
.virt_buffer = nullptr,
.virt_size = 0,
.buf_offset = 16
};
EXPECT_OK(dut.SdioDoRwTxn(3, &txn));
dut.VerifyAll();
mock_sdmmc.VerifyAll();
}
TEST(SdioControllerDeviceTest, SdioDoRwTxnMultiBlock) {
MockSdmmcDevice mock_sdmmc({
.caps = 0,
.max_transfer_size = 32,
.max_transfer_size_non_dma = 32,
.prefs = 0
});
SdioControllerDeviceTest dut(&mock_sdmmc, {
.num_funcs = 0,
.sdio_vsn = 0,
.cccr_vsn = 0,
.caps = SDIO_CARD_MULTI_BLOCK
});
dut.SetSdioFunctionInfo(7, {
.hw_info = {},
.cur_blk_size = 8,
.enabled = true,
.intr_enabled = false
});
mock_sdmmc.mock_SdioIoRwExtended()
.ExpectCall(ZX_OK, SDIO_CARD_MULTI_BLOCK, false, 7, 0xabcd0008, false, 4, 8, 64)
.ExpectCall(ZX_OK, SDIO_CARD_MULTI_BLOCK, false, 7, 0xabcd0008, false, 4, 8, 96)
.ExpectCall(ZX_OK, SDIO_CARD_MULTI_BLOCK, false, 7, 0xabcd0008, false, 1, 4, 128)
.ExpectCall(ZX_OK, SDIO_CARD_MULTI_BLOCK, true, 7, 0x12340008, true, 4, 8, 64)
.ExpectCall(ZX_OK, SDIO_CARD_MULTI_BLOCK, true, 7, 0x12340028, true, 4, 8, 96)
.ExpectCall(ZX_OK, SDIO_CARD_MULTI_BLOCK, true, 7, 0x12340048, true, 1, 4, 128);
sdio_rw_txn_t txn = {
.addr = 0xabcd0008,
.data_size = 68,
.incr = false,
.write = false,
.use_dma = false,
.dma_vmo = ZX_HANDLE_INVALID,
.virt_buffer = nullptr,
.virt_size = 0,
.buf_offset = 64
};
EXPECT_OK(dut.SdioDoRwTxn(7, &txn));
txn = {
.addr = 0x12340008,
.data_size = 68,
.incr = true,
.write = true,
.use_dma = false,
.dma_vmo = ZX_HANDLE_INVALID,
.virt_buffer = nullptr,
.virt_size = 0,
.buf_offset = 64
};
EXPECT_OK(dut.SdioDoRwTxn(7, &txn));
dut.VerifyAll();
mock_sdmmc.VerifyAll();
}
TEST(SdioControllerDeviceTest, DdkLifecycle) {
MockSdmmcDevice mock_sdmmc({});
SdioControllerDeviceTest dut(&mock_sdmmc, {
.num_funcs = 5,
.sdio_vsn = 0,
.cccr_vsn = 0,
.caps = 0
});
Bind ddk;
EXPECT_OK(dut.AddDevice());
dut.DdkUnbind();
ddk.Ok();
EXPECT_EQ(ddk.total_children(), 4);
}
TEST(SdioControllerDeviceTest, SdioIntrPending) {
MockSdmmcDevice mock_sdmmc({});
SdioControllerDeviceTest dut(&mock_sdmmc, {});
dut.mock_SdioDoRwByte()
.ExpectCall({ZX_OK, 0b0011'0010}, false, 0, SDIO_CIA_CCCR_INTx_INTR_PEN_ADDR, 0)
.ExpectCall({ZX_OK, 0b0010'0010}, false, 0, SDIO_CIA_CCCR_INTx_INTR_PEN_ADDR, 0)
.ExpectCall({ZX_OK, 0b1000'0000}, false, 0, SDIO_CIA_CCCR_INTx_INTR_PEN_ADDR, 0)
.ExpectCall({ZX_OK, 0b0000'0000}, false, 0, SDIO_CIA_CCCR_INTx_INTR_PEN_ADDR, 0)
.ExpectCall({ZX_OK, 0b0000'1110}, false, 0, SDIO_CIA_CCCR_INTx_INTR_PEN_ADDR, 0)
.ExpectCall({ZX_OK, 0b0000'1110}, false, 0, SDIO_CIA_CCCR_INTx_INTR_PEN_ADDR, 0)
.ExpectCall({ZX_OK, 0b0000'1110}, false, 0, SDIO_CIA_CCCR_INTx_INTR_PEN_ADDR, 0);
bool pending;
EXPECT_OK(dut.SdioIntrPending(4, &pending));
EXPECT_TRUE(pending);
EXPECT_OK(dut.SdioIntrPending(4, &pending));
EXPECT_FALSE(pending);
EXPECT_OK(dut.SdioIntrPending(7, &pending));
EXPECT_TRUE(pending);
EXPECT_OK(dut.SdioIntrPending(7, &pending));
EXPECT_FALSE(pending);
EXPECT_OK(dut.SdioIntrPending(1, &pending));
EXPECT_TRUE(pending);
EXPECT_OK(dut.SdioIntrPending(2, &pending));
EXPECT_TRUE(pending);
EXPECT_OK(dut.SdioIntrPending(3, &pending));
EXPECT_TRUE(pending);
}
} // namespace sdmmc