blob: c06dae016354448b06122cc7b88dd521f57357ae [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 <lib/fit/defer.h>
#include <lib/fzl/vmo-mapper.h>
#include <lib/sdio/hw.h>
#include <lib/zx/vmo.h>
#include <memory>
#include <fbl/algorithm.h>
#include <fbl/auto_lock.h>
#include <zxtest/zxtest.h>
#include "fake-sdmmc-device.h"
#include "src/devices/testing/mock-ddk/mock-device.h"
namespace {
constexpr uint32_t OpCondFunctions(uint32_t functions) {
return SDIO_SEND_OP_COND_RESP_IORDY | (functions << SDIO_SEND_OP_COND_RESP_NUM_FUNC_LOC);
}
} // namespace
namespace sdmmc {
class SdioControllerDeviceTest : public zxtest::Test {
public:
SdioControllerDeviceTest() {
dut_ = std::make_unique<SdioControllerDevice>(parent_.get(), SdmmcDevice(sdmmc_.GetClient()));
}
void SetUp() override { sdmmc_.Reset(); }
protected:
std::shared_ptr<MockDevice> parent_ = MockDevice::FakeRootParent();
FakeSdmmcDevice sdmmc_;
std::unique_ptr<SdioControllerDevice> dut_;
};
class SdioScatterGatherTest : public zxtest::Test {
public:
SdioScatterGatherTest() {
dut_ = std::make_unique<SdioControllerDevice>(parent_.get(), SdmmcDevice(sdmmc_.GetClient()));
}
void SetUp() override { sdmmc_.Reset(); }
void Init(const uint8_t function, const bool multiblock) {
sdmmc_.set_command_callback(
SDIO_SEND_OP_COND, [](sdmmc_req_t* req) -> void { req->response[0] = OpCondFunctions(5); });
sdmmc_.Write(
SDIO_CIA_CCCR_CARD_CAPS_ADDR,
std::vector<uint8_t>{static_cast<uint8_t>(multiblock ? SDIO_CIA_CCCR_CARD_CAP_SMB : 0)}, 0);
// Set the maximum block size for function 1-5 to eight bytes.
sdmmc_.Write(0x0109, std::vector<uint8_t>{0x00, 0x10, 0x00}, 0);
sdmmc_.Write(0x0209, std::vector<uint8_t>{0x00, 0x10, 0x00}, 0);
sdmmc_.Write(0x0309, std::vector<uint8_t>{0x00, 0x10, 0x00}, 0);
sdmmc_.Write(0x0409, std::vector<uint8_t>{0x00, 0x10, 0x00}, 0);
sdmmc_.Write(0x0509, std::vector<uint8_t>{0x00, 0x10, 0x00}, 0);
sdmmc_.Write(0x1000, std::vector<uint8_t>{0x22, 0x2a, 0x01}, 0);
sdmmc_.Write(0x100e, std::vector<uint8_t>{0x08, 0x00}, 0);
sdmmc_.set_host_info({
.caps = 0,
.max_transfer_size = 1024,
.max_transfer_size_non_dma = 1024,
.prefs = 0,
});
EXPECT_OK(dut_->Init());
EXPECT_OK(dut_->ProbeSdio());
EXPECT_OK(dut_->SdioUpdateBlockSize(function, 4, false));
sdmmc_.requests().clear();
zx::vmo vmo1, vmo3;
ASSERT_OK(mapper1_.CreateAndMap(zx_system_get_page_size(), ZX_VM_PERM_READ | ZX_VM_PERM_WRITE,
nullptr, &vmo1));
ASSERT_OK(mapper2_.CreateAndMap(zx_system_get_page_size(), ZX_VM_PERM_READ | ZX_VM_PERM_WRITE,
nullptr, &vmo2_));
ASSERT_OK(mapper3_.CreateAndMap(zx_system_get_page_size(), ZX_VM_PERM_READ | ZX_VM_PERM_WRITE,
nullptr, &vmo3));
const uint32_t vmo_rights = SDMMC_VMO_RIGHT_READ | SDMMC_VMO_RIGHT_WRITE;
EXPECT_OK(dut_->SdioRegisterVmo(function, 1, std::move(vmo1), 0, zx_system_get_page_size(),
vmo_rights));
EXPECT_OK(dut_->SdioRegisterVmo(function, 3, std::move(vmo3), 8, zx_system_get_page_size() - 8,
vmo_rights));
}
protected:
static constexpr uint8_t kTestData1[] = {0x17, 0xc6, 0xf4, 0x4a, 0x92, 0xc6, 0x09, 0x0a,
0x8c, 0x54, 0x08, 0x07, 0xde, 0x5f, 0x8d, 0x59};
static constexpr uint8_t kTestData2[] = {0x0d, 0x90, 0x85, 0x6a, 0xe2, 0xa9, 0x00, 0x0e,
0xdf, 0x26, 0xe2, 0x17, 0x88, 0x4d, 0x3a, 0x72};
static constexpr uint8_t kTestData3[] = {0x34, 0x83, 0x15, 0x31, 0x29, 0xa8, 0x4b, 0xe8,
0xd9, 0x1f, 0xa4, 0xf4, 0x8d, 0x3a, 0x27, 0x0c};
static sdmmc_buffer_region_t MakeBufferRegion(const zx::vmo& vmo, uint64_t offset,
uint64_t size) {
return {
.buffer =
{
.vmo = vmo.get(),
},
.type = SDMMC_BUFFER_TYPE_VMO_HANDLE,
.offset = offset,
.size = size,
};
}
static sdmmc_buffer_region_t MakeBufferRegion(uint32_t vmo_id, uint64_t offset, uint64_t size) {
return {
.buffer =
{
.vmo_id = vmo_id,
},
.type = SDMMC_BUFFER_TYPE_VMO_ID,
.offset = offset,
.size = size,
};
}
struct SdioCmd53 {
static SdioCmd53 FromArg(uint32_t arg) {
SdioCmd53 ret = {};
ret.blocks_or_bytes = arg & SDIO_IO_RW_EXTD_BYTE_BLK_COUNT_MASK;
ret.address = (arg & SDIO_IO_RW_EXTD_REG_ADDR_MASK) >> SDIO_IO_RW_EXTD_REG_ADDR_LOC;
ret.op_code = (arg & SDIO_IO_RW_EXTD_OP_CODE_INCR) ? 1 : 0;
ret.block_mode = (arg & SDIO_IO_RW_EXTD_BLOCK_MODE) ? 1 : 0;
ret.function_number = (arg & SDIO_IO_RW_EXTD_FN_IDX_MASK) >> SDIO_IO_RW_EXTD_FN_IDX_LOC;
ret.rw_flag = (arg & SDIO_IO_RW_EXTD_RW_FLAG) ? 1 : 0;
return ret;
}
uint32_t blocks_or_bytes;
uint32_t address;
uint32_t op_code;
uint32_t block_mode;
uint32_t function_number;
uint32_t rw_flag;
};
std::shared_ptr<MockDevice> parent_ = MockDevice::FakeRootParent();
FakeSdmmcDevice sdmmc_;
std::unique_ptr<SdioControllerDevice> dut_;
zx::vmo vmo2_;
fzl::VmoMapper mapper1_, mapper2_, mapper3_;
};
TEST_F(SdioControllerDeviceTest, MultiplexInterrupts) {
EXPECT_OK(dut_->StartSdioIrqThread());
auto stop_thread = fit::defer([&]() { dut_->StopSdioIrqThread(); });
zx::port port;
ASSERT_OK(zx::port::create(ZX_PORT_BIND_TO_INTERRUPT, &port));
zx::interrupt interrupt1, interrupt2, interrupt4, interrupt7;
ASSERT_OK(dut_->SdioGetInBandIntr(1, &interrupt1));
ASSERT_OK(dut_->SdioGetInBandIntr(2, &interrupt2));
ASSERT_OK(dut_->SdioGetInBandIntr(4, &interrupt4));
ASSERT_OK(dut_->SdioGetInBandIntr(7, &interrupt7));
ASSERT_OK(interrupt1.bind(port, 1, 0));
ASSERT_OK(interrupt2.bind(port, 2, 0));
ASSERT_OK(interrupt4.bind(port, 4, 0));
ASSERT_OK(interrupt7.bind(port, 7, 0));
sdmmc_.Write(SDIO_CIA_CCCR_INTx_INTR_PEN_ADDR, std::vector<uint8_t>{0b0000'0010}, 0);
sdmmc_.TriggerInBandInterrupt();
zx_port_packet_t packet;
EXPECT_OK(port.wait(zx::time::infinite(), &packet));
EXPECT_EQ(packet.key, 1);
EXPECT_OK(interrupt1.ack());
sdmmc_.Write(SDIO_CIA_CCCR_INTx_INTR_PEN_ADDR, std::vector<uint8_t>{0b1111'1110}, 0);
sdmmc_.TriggerInBandInterrupt();
EXPECT_OK(port.wait(zx::time::infinite(), &packet));
EXPECT_EQ(packet.key, 1);
EXPECT_OK(interrupt1.ack());
EXPECT_OK(port.wait(zx::time::infinite(), &packet));
EXPECT_EQ(packet.key, 2);
EXPECT_OK(interrupt2.ack());
EXPECT_OK(port.wait(zx::time::infinite(), &packet));
EXPECT_EQ(packet.key, 4);
EXPECT_OK(interrupt4.ack());
EXPECT_OK(port.wait(zx::time::infinite(), &packet));
EXPECT_EQ(packet.key, 7);
EXPECT_OK(interrupt7.ack());
sdmmc_.Write(SDIO_CIA_CCCR_INTx_INTR_PEN_ADDR, std::vector<uint8_t>{0b1010'0010}, 0);
sdmmc_.TriggerInBandInterrupt();
EXPECT_OK(port.wait(zx::time::infinite(), &packet));
EXPECT_EQ(packet.key, 1);
EXPECT_OK(interrupt1.ack());
EXPECT_OK(port.wait(zx::time::infinite(), &packet));
EXPECT_EQ(packet.key, 7);
EXPECT_OK(interrupt7.ack());
sdmmc_.Write(SDIO_CIA_CCCR_INTx_INTR_PEN_ADDR, std::vector<uint8_t>{0b0011'0110}, 0);
sdmmc_.TriggerInBandInterrupt();
EXPECT_OK(port.wait(zx::time::infinite(), &packet));
EXPECT_EQ(packet.key, 1);
EXPECT_OK(interrupt1.ack());
EXPECT_OK(port.wait(zx::time::infinite(), &packet));
EXPECT_EQ(packet.key, 2);
EXPECT_OK(interrupt2.ack());
EXPECT_OK(port.wait(zx::time::infinite(), &packet));
EXPECT_EQ(packet.key, 4);
EXPECT_OK(interrupt4.ack());
}
TEST_F(SdioControllerDeviceTest, SdioDoRwTxn) {
// Report five IO functions.
sdmmc_.set_command_callback(
SDIO_SEND_OP_COND, [](sdmmc_req_t* req) -> void { req->response[0] = OpCondFunctions(5); });
sdmmc_.Write(SDIO_CIA_CCCR_CARD_CAPS_ADDR, std::vector<uint8_t>{0x00}, 0);
// Set the maximum block size for function three to eight bytes.
sdmmc_.Write(0x0309, std::vector<uint8_t>{0x00, 0x10, 0x00}, 0);
sdmmc_.Write(0x1000, std::vector<uint8_t>{0x22, 0x2a, 0x01}, 0);
sdmmc_.Write(0x100e, std::vector<uint8_t>{0x08, 0x00}, 0);
sdmmc_.set_host_info({
.caps = 0,
.max_transfer_size = 16,
.max_transfer_size_non_dma = 16,
.prefs = 0,
});
EXPECT_OK(dut_->Init());
EXPECT_OK(dut_->ProbeSdio());
EXPECT_OK(dut_->SdioUpdateBlockSize(3, 0, true));
uint16_t block_size = 0;
EXPECT_OK(dut_->SdioGetBlockSize(3, &block_size));
EXPECT_EQ(block_size, 8);
constexpr uint8_t kTestData[52] = {
0xff, 0x7c, 0xa6, 0x24, 0x6f, 0x69, 0x7a, 0x39, 0x63, 0x68, 0xef, 0x28, 0xf3,
0x18, 0x91, 0xf1, 0x68, 0x48, 0x78, 0x2f, 0xbb, 0xb2, 0x9a, 0x63, 0x51, 0xd4,
0xe1, 0x94, 0xb4, 0x5c, 0x81, 0x94, 0xc7, 0x86, 0x50, 0x33, 0x61, 0xf8, 0x97,
0x4c, 0x68, 0x71, 0x7f, 0x17, 0x59, 0x82, 0xc5, 0x36, 0xe0, 0x20, 0x0b, 0x56,
};
uint8_t buffer[sizeof(kTestData)];
memcpy(buffer, kTestData, sizeof(buffer));
sdio_rw_txn_t txn = {
.addr = 0x1ab08,
.data_size = 36,
.incr = false,
.write = true,
.use_dma = false,
.dma_vmo = ZX_HANDLE_INVALID,
.virt_buffer = buffer,
.virt_size = 0,
.buf_offset = 16,
};
EXPECT_OK(dut_->SdioDoRwTxn(3, &txn));
// The write sequence should be: four writes of blocks of eight, one write of four bytes. This is
// a FIFO write, meaning the data will get overwritten each time. Verify the final state of the
// device.
const std::vector<uint8_t> read_data = sdmmc_.Read(0x1ab08, 16, 3);
EXPECT_BYTES_EQ(read_data.data(), buffer + sizeof(buffer) - 4, 4);
EXPECT_BYTES_EQ(read_data.data() + 4, buffer + sizeof(buffer) - 8, 4);
sdmmc_.Write(0x12308, cpp20::span<const uint8_t>(kTestData, sizeof(kTestData)), 3);
memset(buffer, 0, sizeof(buffer));
txn = {
.addr = 0x12308,
.data_size = 36,
.incr = true,
.write = false,
.use_dma = false,
.dma_vmo = ZX_HANDLE_INVALID,
.virt_buffer = buffer,
.virt_size = 0,
.buf_offset = 16,
};
EXPECT_OK(dut_->SdioDoRwTxn(3, &txn));
EXPECT_BYTES_EQ(buffer + 16, kTestData, 36);
}
TEST_F(SdioControllerDeviceTest, SdioDoRwTxnMultiBlock) {
sdmmc_.set_command_callback(
SDIO_SEND_OP_COND, [](sdmmc_req_t* req) -> void { req->response[0] = OpCondFunctions(7); });
sdmmc_.Write(SDIO_CIA_CCCR_CARD_CAPS_ADDR, std::vector<uint8_t>{SDIO_CIA_CCCR_CARD_CAP_SMB}, 0);
// Set the maximum block size for function seven to eight bytes.
sdmmc_.Write(0x709, std::vector<uint8_t>{0x00, 0x10, 0x00}, 0);
sdmmc_.Write(0x1000, std::vector<uint8_t>{0x22, 0x2a, 0x01}, 0);
sdmmc_.Write(0x100e, std::vector<uint8_t>{0x08, 0x00}, 0);
sdmmc_.set_host_info({
.caps = 0,
.max_transfer_size = 32,
.max_transfer_size_non_dma = 32,
.prefs = 0,
});
EXPECT_OK(dut_->Init());
EXPECT_OK(dut_->ProbeSdio());
EXPECT_OK(dut_->SdioUpdateBlockSize(7, 0, true));
uint16_t block_size = 0;
EXPECT_OK(dut_->SdioGetBlockSize(7, &block_size));
EXPECT_EQ(block_size, 8);
constexpr uint8_t kTestData[132] = {
// clang-format off
0x94, 0xfa, 0x41, 0x93, 0x40, 0x81, 0xae, 0x83, 0x85, 0x88, 0x98, 0x6d,
0x52, 0x1c, 0x53, 0x9c, 0xa7, 0x7a, 0x19, 0x74, 0xc9, 0xa9, 0x47, 0xd9,
0x64, 0x2b, 0x76, 0x47, 0x55, 0x0b, 0x3d, 0x34, 0xd6, 0xfc, 0xca, 0x7b,
0xae, 0xe0, 0xff, 0xe3, 0xa2, 0xd3, 0xe5, 0xb6, 0xbc, 0xa4, 0x3d, 0x01,
0x99, 0x92, 0xdc, 0xac, 0x68, 0xb1, 0x88, 0x22, 0xc4, 0xf4, 0x1a, 0x45,
0xe9, 0xd3, 0x5e, 0x8c, 0x24, 0x98, 0x7b, 0xf5, 0x32, 0x6d, 0xe5, 0x01,
0x36, 0x03, 0x9b, 0xee, 0xfa, 0x23, 0x2f, 0xdd, 0xc6, 0xa4, 0x34, 0x58,
0x23, 0xaa, 0xc9, 0x00, 0x73, 0xb8, 0xe0, 0xd8, 0xde, 0xc4, 0x59, 0x66,
0x76, 0xd3, 0x65, 0xe0, 0xfa, 0xf7, 0x89, 0x40, 0x3a, 0xa8, 0x83, 0x53,
0x63, 0xf4, 0x36, 0xea, 0xb3, 0x94, 0xe7, 0x5f, 0x3c, 0xed, 0x8d, 0x3e,
0xee, 0x1b, 0x75, 0xea, 0xb3, 0x95, 0xd2, 0x25, 0x7c, 0xb9, 0x6d, 0x37,
// clang-format on
};
uint8_t buffer[sizeof(kTestData)] = {};
sdmmc_.Write(0x1ab08, cpp20::span<const uint8_t>(kTestData, sizeof(kTestData)), 7);
sdio_rw_txn_t txn = {
.addr = 0x1ab08,
.data_size = 68,
.incr = false,
.write = false,
.use_dma = false,
.dma_vmo = ZX_HANDLE_INVALID,
.virt_buffer = buffer,
.virt_size = 0,
.buf_offset = 64,
};
EXPECT_OK(dut_->SdioDoRwTxn(7, &txn));
EXPECT_BYTES_EQ(buffer + 64, kTestData, 32);
EXPECT_BYTES_EQ(buffer + 96, kTestData, 32);
EXPECT_BYTES_EQ(buffer + 128, kTestData, 4);
memcpy(buffer, kTestData, sizeof(buffer));
txn = {
.addr = 0x12308,
.data_size = 68,
.incr = true,
.write = true,
.use_dma = false,
.dma_vmo = ZX_HANDLE_INVALID,
.virt_buffer = buffer,
.virt_size = 0,
.buf_offset = 64,
};
EXPECT_OK(dut_->SdioDoRwTxn(7, &txn));
EXPECT_BYTES_EQ(sdmmc_.Read(0x12308, 68, 7).data(), kTestData + 64, 68);
}
TEST_F(SdioControllerDeviceTest, DdkLifecycle) {
// The interrupt thread is started by AddDevice.
auto stop_thread = fit::defer([&]() { dut_->StopSdioIrqThread(); });
sdmmc_.set_command_callback(
SDIO_SEND_OP_COND, [](sdmmc_req_t* req) -> void { req->response[0] = OpCondFunctions(4); });
EXPECT_OK(dut_->ProbeSdio());
EXPECT_OK(dut_->AddDevice());
EXPECT_EQ(parent_->descendant_count(), 5);
dut_->DdkAsyncRemove();
__UNUSED auto ptr = dut_.release();
EXPECT_OK(mock_ddk::ReleaseFlaggedDevices(parent_.get()));
stop_thread.cancel();
}
TEST_F(SdioControllerDeviceTest, SdioIntrPending) {
bool pending;
sdmmc_.Write(SDIO_CIA_CCCR_INTx_INTR_PEN_ADDR, std::vector<uint8_t>{0b0011'0010}, 0);
EXPECT_OK(dut_->SdioIntrPending(4, &pending));
EXPECT_TRUE(pending);
sdmmc_.Write(SDIO_CIA_CCCR_INTx_INTR_PEN_ADDR, std::vector<uint8_t>{0b0010'0010}, 0);
EXPECT_OK(dut_->SdioIntrPending(4, &pending));
EXPECT_FALSE(pending);
sdmmc_.Write(SDIO_CIA_CCCR_INTx_INTR_PEN_ADDR, std::vector<uint8_t>{0b1000'0000}, 0);
EXPECT_OK(dut_->SdioIntrPending(7, &pending));
EXPECT_TRUE(pending);
sdmmc_.Write(SDIO_CIA_CCCR_INTx_INTR_PEN_ADDR, std::vector<uint8_t>{0b0000'0000}, 0);
EXPECT_OK(dut_->SdioIntrPending(7, &pending));
EXPECT_FALSE(pending);
sdmmc_.Write(SDIO_CIA_CCCR_INTx_INTR_PEN_ADDR, std::vector<uint8_t>{0b0000'1110}, 0);
EXPECT_OK(dut_->SdioIntrPending(1, &pending));
EXPECT_TRUE(pending);
sdmmc_.Write(SDIO_CIA_CCCR_INTx_INTR_PEN_ADDR, std::vector<uint8_t>{0b0000'1110}, 0);
EXPECT_OK(dut_->SdioIntrPending(2, &pending));
EXPECT_TRUE(pending);
sdmmc_.Write(SDIO_CIA_CCCR_INTx_INTR_PEN_ADDR, std::vector<uint8_t>{0b0000'1110}, 0);
EXPECT_OK(dut_->SdioIntrPending(3, &pending));
EXPECT_TRUE(pending);
}
TEST_F(SdioControllerDeviceTest, EnableDisableFnIntr) {
sdmmc_.Write(0x04, std::vector<uint8_t>{0b0000'0000}, 0);
EXPECT_OK(dut_->SdioEnableFnIntr(4));
EXPECT_EQ(sdmmc_.Read(0x04, 1, 0)[0], 0b0001'0001);
EXPECT_OK(dut_->SdioEnableFnIntr(7));
EXPECT_EQ(sdmmc_.Read(0x04, 1, 0)[0], 0b1001'0001);
EXPECT_OK(dut_->SdioEnableFnIntr(4));
EXPECT_EQ(sdmmc_.Read(0x04, 1, 0)[0], 0b1001'0001);
EXPECT_OK(dut_->SdioDisableFnIntr(4));
EXPECT_EQ(sdmmc_.Read(0x04, 1, 0)[0], 0b1000'0001);
EXPECT_OK(dut_->SdioDisableFnIntr(7));
EXPECT_EQ(sdmmc_.Read(0x04, 1, 0)[0], 0b0000'0000);
EXPECT_NOT_OK(dut_->SdioDisableFnIntr(7));
}
TEST_F(SdioControllerDeviceTest, ProcessCccr) {
sdmmc_.set_command_callback(
SDIO_SEND_OP_COND, [](sdmmc_req_t* req) -> void { req->response[0] = OpCondFunctions(0); });
sdmmc_.Write(0x00, std::vector<uint8_t>{0x43}, 0); // CCCR/SDIO revision.
sdmmc_.Write(0x08, std::vector<uint8_t>{0xc2}, 0); // Card capability.
sdmmc_.Write(0x13, std::vector<uint8_t>{0xa9}, 0); // Bus speed select.
sdmmc_.Write(0x14, std::vector<uint8_t>{0x3f}, 0); // UHS-I support.
sdmmc_.Write(0x15, std::vector<uint8_t>{0xb7}, 0); // Driver strength.
EXPECT_OK(dut_->ProbeSdio());
sdio_hw_info_t info = {};
EXPECT_OK(dut_->SdioGetDevHwInfo(&info));
EXPECT_EQ(info.dev_hw_info.caps,
SDIO_CARD_MULTI_BLOCK | SDIO_CARD_LOW_SPEED | SDIO_CARD_FOUR_BIT_BUS |
SDIO_CARD_HIGH_SPEED | SDIO_CARD_UHS_SDR50 | SDIO_CARD_UHS_SDR104 |
SDIO_CARD_UHS_DDR50 | SDIO_CARD_TYPE_A | SDIO_CARD_TYPE_B | SDIO_CARD_TYPE_D);
sdmmc_.Write(0x08, std::vector<uint8_t>{0x00}, 0);
sdmmc_.Write(0x13, std::vector<uint8_t>{0x00}, 0);
sdmmc_.Write(0x14, std::vector<uint8_t>{0x00}, 0);
sdmmc_.Write(0x15, std::vector<uint8_t>{0x00}, 0);
EXPECT_OK(dut_->ProbeSdio());
EXPECT_OK(dut_->SdioGetDevHwInfo(&info));
EXPECT_EQ(info.dev_hw_info.caps, 0);
sdmmc_.Write(0x00, std::vector<uint8_t>{0x41}, 0);
EXPECT_NOT_OK(dut_->ProbeSdio());
sdmmc_.Write(0x00, std::vector<uint8_t>{0x33}, 0);
EXPECT_NOT_OK(dut_->ProbeSdio());
}
TEST_F(SdioControllerDeviceTest, ProcessCis) {
sdmmc_.set_command_callback(
SDIO_SEND_OP_COND, [](sdmmc_req_t* req) -> void { req->response[0] = OpCondFunctions(5); });
sdmmc_.Write(0x0000'0509, std::vector<uint8_t>{0xa2, 0xc2, 0x00}, 0); // CIS pointer.
sdmmc_.Write(0x0000'c2a2,
std::vector<uint8_t>{
0x20, // Manufacturer ID tuple.
0x04, // Manufacturer ID tuple size.
0x01, 0xc0, // Manufacturer code.
0xce, 0xfa, // Manufacturer information (part number/revision).
0x00, // Null tuple.
0x22, // Function extensions tuple.
0x2a, // Function extensions tuple size.
0x01, // Type of extended data.
},
0);
sdmmc_.Write(0x0000'c2b7, std::vector<uint8_t>{0x00, 0x01}, 0); // Function block size.
sdmmc_.Write(0x0000'c2d5, std::vector<uint8_t>{0x00}, 0); // End-of-chain tuple.
EXPECT_OK(dut_->ProbeSdio());
sdio_hw_info_t info = {};
EXPECT_OK(dut_->SdioGetDevHwInfo(&info));
EXPECT_EQ(info.dev_hw_info.num_funcs, 6);
EXPECT_EQ(info.funcs_hw_info[5].max_blk_size, 256);
EXPECT_EQ(info.funcs_hw_info[5].manufacturer_id, 0xc001);
EXPECT_EQ(info.funcs_hw_info[5].product_id, 0xface);
}
TEST_F(SdioControllerDeviceTest, ProcessCisFunction0) {
sdmmc_.set_command_callback(
SDIO_SEND_OP_COND, [](sdmmc_req_t* req) -> void { req->response[0] = OpCondFunctions(5); });
sdmmc_.set_host_info({
.caps = 0,
.max_transfer_size = 1024,
.max_transfer_size_non_dma = 1024,
.prefs = 0,
});
EXPECT_OK(dut_->Init());
sdmmc_.Write(0x0000'0009, std::vector<uint8_t>{0xf5, 0x61, 0x01}, 0); // CIS pointer.
sdmmc_.Write(0x0001'61f5,
std::vector<uint8_t>{
0x22, // Function extensions tuple.
0x04, // Function extensions tuple size.
0x00, // Type of extended data.
0x00, 0x02, // Function 0 block size.
0x32, // Max transfer speed.
0x00, // Null tuple.
0x20, // Manufacturer ID tuple.
0x04, // Manufacturer ID tuple size.
0xef, 0xbe, // Manufacturer code.
0xfe, 0xca, // Manufacturer information (part number/revision).
0xff, // End-of-chain tuple.
},
0);
EXPECT_OK(dut_->ProbeSdio());
sdio_hw_info_t info = {};
EXPECT_OK(dut_->SdioGetDevHwInfo(&info));
EXPECT_EQ(info.dev_hw_info.num_funcs, 6);
EXPECT_EQ(info.funcs_hw_info[0].max_blk_size, 512);
EXPECT_EQ(info.funcs_hw_info[0].max_tran_speed, 25000);
EXPECT_EQ(info.funcs_hw_info[0].manufacturer_id, 0xbeef);
EXPECT_EQ(info.funcs_hw_info[0].product_id, 0xcafe);
}
TEST_F(SdioControllerDeviceTest, ProcessFbr) {
sdmmc_.set_command_callback(
SDIO_SEND_OP_COND, [](sdmmc_req_t* req) -> void { req->response[0] = OpCondFunctions(7); });
sdmmc_.Write(0x100, std::vector<uint8_t>{0x83}, 0);
sdmmc_.Write(0x500, std::vector<uint8_t>{0x00}, 0);
sdmmc_.Write(0x600, std::vector<uint8_t>{0xcf}, 0);
sdmmc_.Write(0x601, std::vector<uint8_t>{0xab}, 0);
sdmmc_.Write(0x700, std::vector<uint8_t>{0x4e}, 0);
EXPECT_OK(dut_->ProbeSdio());
sdio_hw_info_t info = {};
EXPECT_OK(dut_->SdioGetDevHwInfo(&info));
EXPECT_EQ(info.dev_hw_info.num_funcs, 8);
EXPECT_EQ(info.funcs_hw_info[1].fn_intf_code, 0x03);
EXPECT_EQ(info.funcs_hw_info[5].fn_intf_code, 0x00);
EXPECT_EQ(info.funcs_hw_info[6].fn_intf_code, 0xab);
EXPECT_EQ(info.funcs_hw_info[7].fn_intf_code, 0x0e);
}
TEST_F(SdioControllerDeviceTest, SmallHostTransferSize) {
sdmmc_.set_command_callback(
SDIO_SEND_OP_COND, [](sdmmc_req_t* req) -> void { req->response[0] = OpCondFunctions(3); });
sdmmc_.set_host_info({
.caps = 0,
.max_transfer_size = 32,
.max_transfer_size_non_dma = 64,
.prefs = 0,
});
EXPECT_OK(dut_->Init());
// Set the maximum block size for function three to 128 bytes.
sdmmc_.Write(0x0309, std::vector<uint8_t>{0x00, 0x10, 0x00}, 0);
sdmmc_.Write(0x1000, std::vector<uint8_t>{0x22, 0x2a, 0x01}, 0);
sdmmc_.Write(0x100e, std::vector<uint8_t>{0x80, 0x00}, 0);
EXPECT_OK(dut_->ProbeSdio());
EXPECT_OK(dut_->SdioUpdateBlockSize(3, 0, true));
uint16_t block_size = 0;
EXPECT_OK(dut_->SdioGetBlockSize(3, &block_size));
EXPECT_EQ(block_size, 128);
constexpr uint8_t kTestData[128] = {
// clang-format off
0x28, 0x52, 0xe3, 0x9a, 0xa5, 0x5f, 0x39, 0x43,
0x7b, 0xb5, 0x24, 0xe7, 0x30, 0x7b, 0x13, 0xc4,
0x28, 0xe6, 0xd5, 0xb5, 0xf9, 0x1d, 0xd4, 0x8b,
0x2e, 0xfb, 0xdc, 0x5e, 0x89, 0x1e, 0xef, 0x8b,
0xa6, 0x7d, 0xf4, 0xb0, 0x87, 0x6f, 0x80, 0x48,
0x71, 0x39, 0x4b, 0x28, 0x3d, 0xf9, 0xa7, 0xbb,
0x8f, 0x13, 0x34, 0x2b, 0xbc, 0xd3, 0x4e, 0xbe,
0xd1, 0x9d, 0x48, 0x1c, 0x79, 0x62, 0x72, 0x48,
0x4b, 0xf0, 0x71, 0x1c, 0x97, 0x99, 0x4d, 0x0f,
0x5a, 0xa1, 0xc2, 0xb5, 0xa1, 0xca, 0x89, 0x34,
0xd9, 0x1a, 0x13, 0xfa, 0xfd, 0x76, 0x74, 0x51,
0xfe, 0x24, 0xd1, 0xc3, 0x89, 0x53, 0x36, 0x14,
0xbd, 0x66, 0x59, 0xba, 0xc9, 0x3b, 0x9e, 0x0f,
0x8f, 0x6b, 0x26, 0x72, 0x72, 0x76, 0x70, 0x68,
0xd6, 0x5f, 0x3b, 0x6e, 0x2e, 0xda, 0x51, 0xf7,
0x55, 0x8b, 0x0e, 0xed, 0x93, 0x71, 0x48, 0xc2,
// clang-format on
};
zx::vmo vmo;
ASSERT_OK(zx::vmo::create(
fbl::round_up<size_t, size_t>(sizeof(kTestData), zx_system_get_page_size()), 0, &vmo));
ASSERT_OK(vmo.write(kTestData, 0, sizeof(kTestData)));
uint8_t buffer[sizeof(kTestData)];
memcpy(buffer, kTestData, sizeof(buffer));
sdio_rw_txn_t txn = {
.addr = 0,
.data_size = 64,
.incr = false,
.write = true,
.use_dma = true,
.dma_vmo = vmo.get(),
.virt_buffer = buffer,
.virt_size = 0,
.buf_offset = 0,
};
EXPECT_NOT_OK(dut_->SdioDoRwTxn(3, &txn));
txn.use_dma = false;
EXPECT_OK(dut_->SdioDoRwTxn(3, &txn));
EXPECT_BYTES_EQ(sdmmc_.Read(0, 64, 3).data(), kTestData, 64);
txn.data_size = 128;
EXPECT_NOT_OK(dut_->SdioDoRwTxn(3, &txn));
txn.use_dma = true;
EXPECT_NOT_OK(dut_->SdioDoRwTxn(3, &txn));
}
TEST_F(SdioControllerDeviceTest, ProbeFail) {
sdmmc_.set_command_callback(
SDIO_SEND_OP_COND, [](sdmmc_req_t* req) -> void { req->response[0] = OpCondFunctions(5); });
// Set the function 3 CIS pointer to zero. This should cause InitFunc and subsequently ProbeSdio
// to fail.
sdmmc_.Write(0x0309, std::vector<uint8_t>{0x00, 0x00, 0x00}, 0);
EXPECT_NOT_OK(dut_->ProbeSdio());
}
TEST_F(SdioControllerDeviceTest, ProbeSdr104) {
sdmmc_.set_command_callback(SDIO_SEND_OP_COND, [](sdmmc_req_t* req) -> void {
req->response[0] = OpCondFunctions(5) | SDIO_SEND_OP_COND_RESP_S18A;
});
sdmmc_.Write(0x0014, std::vector<uint8_t>{0x07}, 0);
sdmmc_.set_host_info({
.caps = SDMMC_HOST_CAP_VOLTAGE_330 | SDMMC_HOST_CAP_SDR104 | SDMMC_HOST_CAP_SDR50 |
SDMMC_HOST_CAP_DDR50,
.max_transfer_size = 0x1000,
.max_transfer_size_non_dma = 0x1000,
.prefs = 0,
});
EXPECT_OK(dut_->Init());
EXPECT_OK(dut_->ProbeSdio());
EXPECT_EQ(sdmmc_.signal_voltage(), SDMMC_VOLTAGE_V180);
EXPECT_EQ(sdmmc_.bus_width(), SDMMC_BUS_WIDTH_FOUR);
EXPECT_EQ(sdmmc_.bus_freq(), 208'000'000);
EXPECT_EQ(sdmmc_.timing(), SDMMC_TIMING_SDR104);
}
TEST_F(SdioControllerDeviceTest, ProbeSdr50LimitedByHost) {
sdmmc_.set_command_callback(SDIO_SEND_OP_COND, [](sdmmc_req_t* req) -> void {
req->response[0] = OpCondFunctions(5) | SDIO_SEND_OP_COND_RESP_S18A;
});
sdmmc_.Write(0x0014, std::vector<uint8_t>{0x07}, 0);
sdmmc_.set_host_info({
.caps = SDMMC_HOST_CAP_VOLTAGE_330 | SDMMC_HOST_CAP_SDR50,
.max_transfer_size = 0x1000,
.max_transfer_size_non_dma = 0x1000,
.prefs = 0,
});
EXPECT_OK(dut_->Init());
EXPECT_OK(dut_->ProbeSdio());
EXPECT_EQ(sdmmc_.signal_voltage(), SDMMC_VOLTAGE_V180);
EXPECT_EQ(sdmmc_.bus_width(), SDMMC_BUS_WIDTH_FOUR);
EXPECT_EQ(sdmmc_.bus_freq(), 100'000'000);
EXPECT_EQ(sdmmc_.timing(), SDMMC_TIMING_SDR50);
}
TEST_F(SdioControllerDeviceTest, ProbeSdr50LimitedByCard) {
sdmmc_.set_command_callback(SDIO_SEND_OP_COND, [](sdmmc_req_t* req) -> void {
req->response[0] = OpCondFunctions(5) | SDIO_SEND_OP_COND_RESP_S18A;
});
sdmmc_.Write(0x0014, std::vector<uint8_t>{0x01}, 0);
sdmmc_.set_host_info({
.caps = SDMMC_HOST_CAP_VOLTAGE_330 | SDMMC_HOST_CAP_SDR104 | SDMMC_HOST_CAP_SDR50 |
SDMMC_HOST_CAP_DDR50,
.max_transfer_size = 0x1000,
.max_transfer_size_non_dma = 0x1000,
.prefs = 0,
});
EXPECT_OK(dut_->Init());
EXPECT_OK(dut_->ProbeSdio());
EXPECT_EQ(sdmmc_.signal_voltage(), SDMMC_VOLTAGE_V180);
EXPECT_EQ(sdmmc_.bus_width(), SDMMC_BUS_WIDTH_FOUR);
EXPECT_EQ(sdmmc_.bus_freq(), 100'000'000);
EXPECT_EQ(sdmmc_.timing(), SDMMC_TIMING_SDR50);
}
TEST_F(SdioControllerDeviceTest, ProbeFallBackToHs) {
sdmmc_.set_command_callback(SDIO_SEND_OP_COND, [](sdmmc_req_t* req) -> void {
req->response[0] = OpCondFunctions(5) | SDIO_SEND_OP_COND_RESP_S18A;
});
sdmmc_.Write(0x0008, std::vector<uint8_t>{0x00}, 0);
sdmmc_.Write(0x0014, std::vector<uint8_t>{0x07}, 0);
sdmmc_.set_perform_tuning_status(ZX_ERR_IO);
sdmmc_.set_host_info({
.caps = SDMMC_HOST_CAP_VOLTAGE_330 | SDMMC_HOST_CAP_SDR104 | SDMMC_HOST_CAP_SDR50 |
SDMMC_HOST_CAP_DDR50,
.max_transfer_size = 0x1000,
.max_transfer_size_non_dma = 0x1000,
.prefs = 0,
});
EXPECT_OK(dut_->Init());
EXPECT_OK(dut_->ProbeSdio());
EXPECT_EQ(sdmmc_.signal_voltage(), SDMMC_VOLTAGE_V180);
EXPECT_EQ(sdmmc_.bus_width(), SDMMC_BUS_WIDTH_FOUR);
EXPECT_EQ(sdmmc_.bus_freq(), 50'000'000);
EXPECT_EQ(sdmmc_.timing(), SDMMC_TIMING_HS);
}
TEST_F(SdioControllerDeviceTest, ProbeSetVoltage) {
sdmmc_.set_command_callback(
SDIO_SEND_OP_COND, [](sdmmc_req_t* req) -> void { req->response[0] = OpCondFunctions(5); });
EXPECT_OK(dut_->ProbeSdio());
// Card does not report 1.8V support so we don't request a change from the host.
EXPECT_EQ(sdmmc_.signal_voltage(), SDMMC_VOLTAGE_MAX);
sdmmc_.set_command_callback(SDIO_SEND_OP_COND, [](sdmmc_req_t* req) -> void {
req->response[0] = OpCondFunctions(5) | SDIO_SEND_OP_COND_RESP_S18A;
});
EXPECT_OK(dut_->ProbeSdio());
EXPECT_EQ(sdmmc_.signal_voltage(), SDMMC_VOLTAGE_V180);
}
TEST_F(SdioControllerDeviceTest, ProbeRetriesRequests) {
sdmmc_.set_command_callback(SDIO_SEND_OP_COND, [](sdmmc_req_t* req) -> void {
req->response[0] = OpCondFunctions(5) | SDIO_SEND_OP_COND_RESP_S18A;
});
uint32_t tries = 0;
sdmmc_.set_command_callback(SDIO_IO_RW_DIRECT, [&](sdmmc_req_t* req) -> void {
const bool write = req->arg & SDIO_IO_RW_DIRECT_RW_FLAG;
const uint32_t fn_idx =
(req->arg & SDIO_IO_RW_DIRECT_FN_IDX_MASK) >> SDIO_IO_RW_DIRECT_FN_IDX_LOC;
const uint32_t addr =
(req->arg & SDIO_IO_RW_DIRECT_REG_ADDR_MASK) >> SDIO_IO_RW_DIRECT_REG_ADDR_LOC;
const bool read_fn0_fbr = !write && (fn_idx == 0) && (addr == SDIO_CIA_FBR_CIS_ADDR);
req->status = (read_fn0_fbr && tries++ < 7) ? ZX_ERR_IO : ZX_OK;
});
EXPECT_OK(dut_->Init());
EXPECT_OK(dut_->ProbeSdio());
}
TEST_F(SdioControllerDeviceTest, IoAbortSetsAbortFlag) {
sdmmc_.set_command_callback(
SDIO_SEND_OP_COND, [](sdmmc_req_t* req) -> void { req->response[0] = OpCondFunctions(5); });
EXPECT_OK(dut_->ProbeSdio());
sdmmc_.set_command_callback(SDIO_IO_RW_DIRECT, [](sdmmc_req_t* req) -> void {
EXPECT_EQ(req->cmd_idx, SDIO_IO_RW_DIRECT);
EXPECT_FALSE(req->cmd_flags & SDMMC_CMD_TYPE_ABORT);
EXPECT_EQ(req->arg, 0xb024'68ab);
});
EXPECT_OK(dut_->SdioDoRwByte(true, 3, 0x1234, 0xab, nullptr));
sdmmc_.set_command_callback(SDIO_IO_RW_DIRECT, [](sdmmc_req_t* req) -> void {
EXPECT_EQ(req->cmd_idx, SDIO_IO_RW_DIRECT);
EXPECT_TRUE(req->cmd_flags & SDMMC_CMD_TYPE_ABORT);
EXPECT_EQ(req->arg, 0x8000'0c03);
});
EXPECT_OK(dut_->SdioIoAbort(3));
}
TEST_F(SdioControllerDeviceTest, DifferentManufacturerProductIds) {
auto stop_thread = fit::defer([&]() { dut_->StopSdioIrqThread(); });
sdmmc_.set_command_callback(
SDIO_SEND_OP_COND, [](sdmmc_req_t* req) -> void { req->response[0] = OpCondFunctions(4); });
EXPECT_OK(dut_->Init());
// Function 0-4 CIS pointers.
sdmmc_.Write(0x0000'0009, std::vector<uint8_t>{0xf5, 0x61, 0x01}, 0);
sdmmc_.Write(0x0000'0109, std::vector<uint8_t>{0xa0, 0x56, 0x00}, 0);
sdmmc_.Write(0x0000'0209, std::vector<uint8_t>{0xe9, 0xc3, 0x00}, 0);
sdmmc_.Write(0x0000'0309, std::vector<uint8_t>{0xb7, 0x6e, 0x01}, 0);
sdmmc_.Write(0x0000'0409, std::vector<uint8_t>{0x86, 0xb7, 0x00}, 0);
sdmmc_.Write(0x0001'61f5,
std::vector<uint8_t>{
0x20, // Manufacturer ID tuple.
0x04, // Manufacturer ID tuple size.
0xef, 0xbe, // Manufacturer code.
0xfe, 0xca, // Manufacturer information (part number/revision).
0xff, // End-of-chain tuple.
},
0);
sdmmc_.Write(0x0000'56a0,
std::vector<uint8_t>{
0x20,
0x04,
0x7b,
0x31,
0x8f,
0xa8,
0xff,
},
0);
sdmmc_.Write(0x0000'c3e9,
std::vector<uint8_t>{
0x20,
0x04,
0xbd,
0x6d,
0x0d,
0x24,
0xff,
},
0);
sdmmc_.Write(0x0001'6eb7,
std::vector<uint8_t>{
0x20,
0x04,
0xca,
0xb8,
0x52,
0x98,
0xff,
},
0);
sdmmc_.Write(0x0000'b786,
std::vector<uint8_t>{
0x20,
0x04,
0xee,
0xf5,
0xde,
0x30,
0xff,
},
0);
EXPECT_OK(dut_->ProbeSdio());
sdio_hw_info_t info = {};
EXPECT_OK(dut_->SdioGetDevHwInfo(&info));
EXPECT_EQ(info.dev_hw_info.num_funcs, 5);
EXPECT_EQ(info.funcs_hw_info[0].manufacturer_id, 0xbeef);
EXPECT_EQ(info.funcs_hw_info[0].product_id, 0xcafe);
EXPECT_OK(dut_->AddDevice());
constexpr zx_device_prop_t kExpectedProps[4][3] = {
{
{BIND_SDIO_VID, 0, 0x317b},
{BIND_SDIO_PID, 0, 0xa88f},
{BIND_SDIO_FUNCTION, 0, 1},
},
{
{BIND_SDIO_VID, 0, 0x6dbd},
{BIND_SDIO_PID, 0, 0x240d},
{BIND_SDIO_FUNCTION, 0, 2},
},
{
{BIND_SDIO_VID, 0, 0xb8ca},
{BIND_SDIO_PID, 0, 0x9852},
{BIND_SDIO_FUNCTION, 0, 3},
},
{
{BIND_SDIO_VID, 0, 0xf5ee},
{BIND_SDIO_PID, 0, 0x30de},
{BIND_SDIO_FUNCTION, 0, 4},
},
};
EXPECT_EQ(parent_->descendant_count(), std::size(kExpectedProps) + 1);
auto controller = parent_->GetLatestChild();
ASSERT_NE(controller, nullptr);
auto iter = controller->children().begin();
for (size_t i = 0; i < std::size(kExpectedProps); i++) {
ASSERT_NE(iter, controller->children().end());
cpp20::span child = (*iter)->GetProperties();
ASSERT_EQ(child.size(), std::size(kExpectedProps[0]));
for (size_t j = 0; j < std::size(kExpectedProps[0]); j++) {
const zx_device_prop_t& prop = child[j];
EXPECT_EQ(prop.id, kExpectedProps[i][j].id);
EXPECT_EQ(prop.reserved, kExpectedProps[i][j].reserved);
EXPECT_EQ(prop.value, kExpectedProps[i][j].value);
}
iter++;
}
dut_->DdkAsyncRemove();
EXPECT_OK(mock_ddk::ReleaseFlaggedDevices(parent_.get()));
__UNUSED auto ptr = dut_.release();
stop_thread.cancel();
}
TEST_F(SdioControllerDeviceTest, RunDiagnostics) {
sdmmc_.set_command_callback(
SDIO_SEND_OP_COND, [](sdmmc_req_t* req) -> void { req->response[0] = OpCondFunctions(4); });
sdmmc_.set_host_info({
.caps = SDMMC_HOST_CAP_SDR104, // Make the SDIO driver call PerformTuning.
.max_transfer_size = 16,
.max_transfer_size_non_dma = 16,
.prefs = 0,
});
EXPECT_OK(dut_->Init());
EXPECT_OK(dut_->ProbeSdio());
dut_->SdioRunDiagnostics();
}
TEST_F(SdioScatterGatherTest, ScatterGatherByteMode) {
Init(3, true);
memcpy(mapper1_.start(), kTestData1, sizeof(kTestData1));
memcpy(mapper2_.start(), kTestData2, sizeof(kTestData2));
memcpy(mapper3_.start(), kTestData3, sizeof(kTestData3));
sdmmc_buffer_region_t buffers[3];
buffers[0] = MakeBufferRegion(1, 8, 2);
buffers[1] = MakeBufferRegion(vmo2_, 4, 1);
buffers[2] = MakeBufferRegion(3, 0, 2);
sdio_rw_txn_new_t txn = {
.addr = 0x1000,
.incr = true,
.write = true,
.buffers_list = buffers,
.buffers_count = countof(buffers),
};
EXPECT_OK(dut_->SdioDoRwTxnNew(3, &txn));
std::vector<uint8_t> actual = sdmmc_.Read(0x1000, 6, 3);
EXPECT_BYTES_EQ(actual.data(), kTestData1 + 8, 2);
EXPECT_BYTES_EQ(actual.data() + 2, kTestData2 + 4, 1);
EXPECT_BYTES_EQ(actual.data() + 3, kTestData3 + 8, 2);
EXPECT_EQ(actual[5], 0xff);
ASSERT_EQ(sdmmc_.requests().size(), 2);
const SdioCmd53 req1 = SdioCmd53::FromArg(sdmmc_.requests()[0].arg);
EXPECT_EQ(req1.blocks_or_bytes, 4);
EXPECT_EQ(req1.address, 0x1000);
EXPECT_EQ(req1.op_code, 1);
EXPECT_EQ(req1.block_mode, 0);
EXPECT_EQ(req1.function_number, 3);
EXPECT_EQ(req1.rw_flag, 1);
const SdioCmd53 req2 = SdioCmd53::FromArg(sdmmc_.requests()[1].arg);
EXPECT_EQ(req2.blocks_or_bytes, 1);
EXPECT_EQ(req2.address, 0x1000 + 4);
EXPECT_EQ(req2.op_code, 1);
EXPECT_EQ(req2.block_mode, 0);
EXPECT_EQ(req2.function_number, 3);
EXPECT_EQ(req2.rw_flag, 1);
}
TEST_F(SdioScatterGatherTest, ScatterGatherBlockMode) {
Init(3, true);
sdmmc_buffer_region_t buffers[3];
buffers[0] = MakeBufferRegion(1, 8, 7);
buffers[1] = MakeBufferRegion(vmo2_, 4, 3);
buffers[2] = MakeBufferRegion(3, 10, 5);
sdmmc_.Write(0x5000, cpp20::span(kTestData1, countof(kTestData1)), 3);
sdio_rw_txn_new_t txn = {
.addr = 0x5000,
.incr = false,
.write = false,
.buffers_list = buffers,
.buffers_count = countof(buffers),
};
EXPECT_OK(dut_->SdioDoRwTxnNew(3, &txn));
EXPECT_BYTES_EQ(static_cast<uint8_t*>(mapper1_.start()) + 8, kTestData1, 7);
EXPECT_BYTES_EQ(static_cast<uint8_t*>(mapper2_.start()) + 4, kTestData1 + 7, 3);
EXPECT_BYTES_EQ(static_cast<uint8_t*>(mapper3_.start()) + 18, kTestData1 + 10, 2);
ASSERT_EQ(sdmmc_.requests().size(), 2);
const SdioCmd53 req1 = SdioCmd53::FromArg(sdmmc_.requests()[0].arg);
EXPECT_EQ(req1.blocks_or_bytes, 3);
EXPECT_EQ(req1.address, 0x5000);
EXPECT_EQ(req1.op_code, 0);
EXPECT_EQ(req1.block_mode, 1);
EXPECT_EQ(req1.function_number, 3);
EXPECT_EQ(req1.rw_flag, 0);
const SdioCmd53 req2 = SdioCmd53::FromArg(sdmmc_.requests()[1].arg);
EXPECT_EQ(req2.blocks_or_bytes, 3);
EXPECT_EQ(req2.address, 0x5000);
EXPECT_EQ(req2.op_code, 0);
EXPECT_EQ(req2.block_mode, 0);
EXPECT_EQ(req2.function_number, 3);
EXPECT_EQ(req2.rw_flag, 0);
}
TEST_F(SdioScatterGatherTest, ScatterGatherBlockModeNoMultiBlock) {
Init(5, false);
memcpy(mapper1_.start(), kTestData1, sizeof(kTestData1));
memcpy(mapper2_.start(), kTestData2, sizeof(kTestData2));
memcpy(mapper3_.start(), kTestData3, sizeof(kTestData3));
sdmmc_buffer_region_t buffers[3];
buffers[0] = MakeBufferRegion(1, 8, 7);
buffers[1] = MakeBufferRegion(vmo2_, 4, 3);
buffers[2] = MakeBufferRegion(3, 0, 5);
sdio_rw_txn_new_t txn = {
.addr = 0x1000,
.incr = true,
.write = true,
.buffers_list = buffers,
.buffers_count = countof(buffers),
};
EXPECT_OK(dut_->SdioDoRwTxnNew(5, &txn));
std::vector<uint8_t> actual = sdmmc_.Read(0x1000, 16, 5);
EXPECT_BYTES_EQ(actual.data(), kTestData1 + 8, 7);
EXPECT_BYTES_EQ(actual.data() + 7, kTestData2 + 4, 3);
EXPECT_BYTES_EQ(actual.data() + 10, kTestData3 + 8, 5);
EXPECT_EQ(actual[15], 0xff);
ASSERT_EQ(sdmmc_.requests().size(), 4);
const SdioCmd53 req1 = SdioCmd53::FromArg(sdmmc_.requests()[0].arg);
EXPECT_EQ(req1.blocks_or_bytes, 4);
EXPECT_EQ(req1.address, 0x1000);
EXPECT_EQ(req1.op_code, 1);
EXPECT_EQ(req1.block_mode, 0);
EXPECT_EQ(req1.function_number, 5);
EXPECT_EQ(req1.rw_flag, 1);
const SdioCmd53 req2 = SdioCmd53::FromArg(sdmmc_.requests()[1].arg);
EXPECT_EQ(req2.blocks_or_bytes, 4);
EXPECT_EQ(req2.address, 0x1000 + 4);
EXPECT_EQ(req2.op_code, 1);
EXPECT_EQ(req2.block_mode, 0);
EXPECT_EQ(req2.function_number, 5);
EXPECT_EQ(req2.rw_flag, 1);
const SdioCmd53 req3 = SdioCmd53::FromArg(sdmmc_.requests()[2].arg);
EXPECT_EQ(req3.blocks_or_bytes, 4);
EXPECT_EQ(req3.address, 0x1000 + 8);
EXPECT_EQ(req3.op_code, 1);
EXPECT_EQ(req3.block_mode, 0);
EXPECT_EQ(req3.function_number, 5);
EXPECT_EQ(req3.rw_flag, 1);
const SdioCmd53 req4 = SdioCmd53::FromArg(sdmmc_.requests()[3].arg);
EXPECT_EQ(req4.blocks_or_bytes, 3);
EXPECT_EQ(req4.address, 0x1000 + 12);
EXPECT_EQ(req4.op_code, 1);
EXPECT_EQ(req4.block_mode, 0);
EXPECT_EQ(req4.function_number, 5);
EXPECT_EQ(req4.rw_flag, 1);
}
TEST_F(SdioScatterGatherTest, ScatterGatherBlockModeMultipleFinalBuffers) {
Init(1, true);
sdmmc_.Write(0x3000, cpp20::span(kTestData1, countof(kTestData1)), 1);
sdmmc_buffer_region_t buffers[4];
buffers[0] = MakeBufferRegion(1, 8, 7);
buffers[1] = MakeBufferRegion(vmo2_, 4, 3);
buffers[2] = MakeBufferRegion(3, 0, 3);
buffers[3] = MakeBufferRegion(1, 0, 2);
sdio_rw_txn_new_t txn = {
.addr = 0x3000,
.incr = true,
.write = false,
.buffers_list = buffers,
.buffers_count = countof(buffers),
};
EXPECT_OK(dut_->SdioDoRwTxnNew(1, &txn));
EXPECT_BYTES_EQ(static_cast<uint8_t*>(mapper1_.start()) + 8, kTestData1, 7);
EXPECT_BYTES_EQ(static_cast<uint8_t*>(mapper2_.start()) + 4, kTestData1 + 7, 3);
EXPECT_BYTES_EQ(static_cast<uint8_t*>(mapper3_.start()) + 8, kTestData1 + 10, 3);
EXPECT_BYTES_EQ(static_cast<uint8_t*>(mapper1_.start()), kTestData1 + 13, 2);
ASSERT_EQ(sdmmc_.requests().size(), 2);
const SdioCmd53 req1 = SdioCmd53::FromArg(sdmmc_.requests()[0].arg);
EXPECT_EQ(req1.blocks_or_bytes, 3);
EXPECT_EQ(req1.address, 0x3000);
EXPECT_EQ(req1.op_code, 1);
EXPECT_EQ(req1.block_mode, 1);
EXPECT_EQ(req1.function_number, 1);
EXPECT_EQ(req1.rw_flag, 0);
const SdioCmd53 req2 = SdioCmd53::FromArg(sdmmc_.requests()[1].arg);
EXPECT_EQ(req2.blocks_or_bytes, 3);
EXPECT_EQ(req2.address, 0x3000 + 12);
EXPECT_EQ(req2.op_code, 1);
EXPECT_EQ(req2.block_mode, 0);
EXPECT_EQ(req2.function_number, 1);
EXPECT_EQ(req2.rw_flag, 0);
}
TEST_F(SdioScatterGatherTest, ScatterGatherBlockModeLastAligned) {
Init(3, true);
memcpy(mapper1_.start(), kTestData1, sizeof(kTestData1));
memcpy(mapper2_.start(), kTestData2, sizeof(kTestData2));
memcpy(mapper3_.start(), kTestData3, sizeof(kTestData3));
sdmmc_buffer_region_t buffers[3];
buffers[0] = MakeBufferRegion(1, 8, 7);
buffers[1] = MakeBufferRegion(vmo2_, 4, 5);
buffers[2] = MakeBufferRegion(3, 0, 3);
sdio_rw_txn_new_t txn = {
.addr = 0x1000,
.incr = true,
.write = true,
.buffers_list = buffers,
.buffers_count = countof(buffers),
};
EXPECT_OK(dut_->SdioDoRwTxnNew(3, &txn));
std::vector<uint8_t> actual = sdmmc_.Read(0x1000, 16, 3);
EXPECT_BYTES_EQ(actual.data(), kTestData1 + 8, 7);
EXPECT_BYTES_EQ(actual.data() + 7, kTestData2 + 4, 5);
EXPECT_BYTES_EQ(actual.data() + 12, kTestData3 + 8, 3);
EXPECT_EQ(actual[15], 0xff);
ASSERT_EQ(sdmmc_.requests().size(), 2);
const SdioCmd53 req1 = SdioCmd53::FromArg(sdmmc_.requests()[0].arg);
EXPECT_EQ(req1.blocks_or_bytes, 3);
EXPECT_EQ(req1.address, 0x1000);
EXPECT_EQ(req1.op_code, 1);
EXPECT_EQ(req1.block_mode, 1);
EXPECT_EQ(req1.function_number, 3);
EXPECT_EQ(req1.rw_flag, 1);
const SdioCmd53 req2 = SdioCmd53::FromArg(sdmmc_.requests()[1].arg);
EXPECT_EQ(req2.blocks_or_bytes, 3);
EXPECT_EQ(req2.address, 0x1000 + 12);
EXPECT_EQ(req2.op_code, 1);
EXPECT_EQ(req2.block_mode, 0);
EXPECT_EQ(req2.function_number, 3);
EXPECT_EQ(req2.rw_flag, 1);
}
TEST_F(SdioScatterGatherTest, ScatterGatherOnlyFullBlocks) {
Init(3, true);
memcpy(mapper1_.start(), kTestData1, sizeof(kTestData1));
memcpy(mapper2_.start(), kTestData2, sizeof(kTestData2));
memcpy(mapper3_.start(), kTestData3, sizeof(kTestData3));
sdmmc_buffer_region_t buffers[3];
buffers[0] = MakeBufferRegion(1, 8, 7);
buffers[1] = MakeBufferRegion(vmo2_, 4, 5);
buffers[2] = MakeBufferRegion(3, 0, 4);
sdio_rw_txn_new_t txn = {
.addr = 0x1000,
.incr = true,
.write = true,
.buffers_list = buffers,
.buffers_count = countof(buffers),
};
EXPECT_OK(dut_->SdioDoRwTxnNew(3, &txn));
std::vector<uint8_t> actual = sdmmc_.Read(0x1000, 17, 3);
EXPECT_BYTES_EQ(actual.data(), kTestData1 + 8, 7);
EXPECT_BYTES_EQ(actual.data() + 7, kTestData2 + 4, 5);
EXPECT_BYTES_EQ(actual.data() + 12, kTestData3 + 8, 4);
EXPECT_EQ(actual[16], 0xff);
ASSERT_EQ(sdmmc_.requests().size(), 1);
const SdioCmd53 req1 = SdioCmd53::FromArg(sdmmc_.requests()[0].arg);
EXPECT_EQ(req1.blocks_or_bytes, 4);
EXPECT_EQ(req1.address, 0x1000);
EXPECT_EQ(req1.op_code, 1);
EXPECT_EQ(req1.block_mode, 1);
EXPECT_EQ(req1.function_number, 3);
EXPECT_EQ(req1.rw_flag, 1);
}
TEST_F(SdioScatterGatherTest, ScatterGatherOverMaxTransferSize) {
Init(3, true);
memcpy(mapper1_.start(), kTestData1, sizeof(kTestData1));
memcpy(mapper2_.start(), kTestData2, sizeof(kTestData2));
memcpy(mapper3_.start(), kTestData3, sizeof(kTestData3));
sdmmc_buffer_region_t buffers[3];
buffers[0] = MakeBufferRegion(1, 8, 300 * 4);
buffers[1] = MakeBufferRegion(vmo2_, 4, 800 * 4);
buffers[2] = MakeBufferRegion(3, 0, 100);
sdio_rw_txn_new_t txn = {
.addr = 0x1000,
.incr = true,
.write = true,
.buffers_list = buffers,
.buffers_count = countof(buffers),
};
EXPECT_OK(dut_->SdioDoRwTxnNew(3, &txn));
ASSERT_EQ(sdmmc_.requests().size(), 3);
const SdioCmd53 req1 = SdioCmd53::FromArg(sdmmc_.requests()[0].arg);
EXPECT_EQ(req1.blocks_or_bytes, 511);
EXPECT_EQ(req1.address, 0x1000);
EXPECT_EQ(req1.op_code, 1);
EXPECT_EQ(req1.block_mode, 1);
EXPECT_EQ(req1.function_number, 3);
EXPECT_EQ(req1.rw_flag, 1);
const SdioCmd53 req2 = SdioCmd53::FromArg(sdmmc_.requests()[1].arg);
EXPECT_EQ(req2.blocks_or_bytes, 511);
EXPECT_EQ(req2.address, 0x1000 + (511 * 4));
EXPECT_EQ(req2.op_code, 1);
EXPECT_EQ(req2.block_mode, 1);
EXPECT_EQ(req2.function_number, 3);
EXPECT_EQ(req2.rw_flag, 1);
const SdioCmd53 req3 = SdioCmd53::FromArg(sdmmc_.requests()[2].arg);
EXPECT_EQ(req3.blocks_or_bytes, 103);
EXPECT_EQ(req3.address, 0x1000 + (511 * 4 * 2));
EXPECT_EQ(req3.op_code, 1);
EXPECT_EQ(req3.block_mode, 1);
EXPECT_EQ(req3.function_number, 3);
EXPECT_EQ(req3.rw_flag, 1);
}
} // namespace sdmmc