DO NOT SUBMIT: Add SDMMC hardware test
Change-Id: Ia4c4685a713094ce776b43fd0a3232347656e650
diff --git a/src/devices/block/drivers/sdmmc/BUILD.gn b/src/devices/block/drivers/sdmmc/BUILD.gn
index 844582e..abee53a 100644
--- a/src/devices/block/drivers/sdmmc/BUILD.gn
+++ b/src/devices/block/drivers/sdmmc/BUILD.gn
@@ -141,5 +141,6 @@
deps = [
":sdmmc-bind_test",
":sdmmc-test",
+ "hardware-test",
]
}
diff --git a/src/devices/block/drivers/sdmmc/hardware-test/BUILD.gn b/src/devices/block/drivers/sdmmc/hardware-test/BUILD.gn
new file mode 100644
index 0000000..94910f5
--- /dev/null
+++ b/src/devices/block/drivers/sdmmc/hardware-test/BUILD.gn
@@ -0,0 +1,38 @@
+# 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.
+
+import("//build/components.gni")
+
+executable("hardware-test-bin") {
+ output_name = "sdmmc_hardware_test"
+ sources = [
+ "hardware-test.cc",
+ "main.cc",
+ "sdmmc-test-device-controller.cc",
+ ]
+ deps = [
+ "//sdk/banjo/fuchsia.hardware.sdio:fuchsia.hardware.sdio_banjo_cpp",
+ "//sdk/fidl/fuchsia.device:fuchsia.device_llcpp",
+ "//sdk/fidl/fuchsia.hardware.gpio:fuchsia.hardware.gpio_llcpp",
+ "//sdk/fidl/fuchsia.hardware.i2c:fuchsia.hardware.i2c_llcpp",
+ "//sdk/fidl/fuchsia.hardware.sdio:fuchsia.hardware.sdio_llcpp",
+ "//sdk/fidl/fuchsia.sysinfo:fuchsia.sysinfo_llcpp",
+ "//sdk/lib/fdio",
+ "//zircon/system/ulib/fbl",
+ "//zircon/system/ulib/hwreg",
+ "//zircon/system/ulib/zxc",
+ "//zircon/system/ulib/zxtest",
+ ]
+}
+
+# fuchsia_test_component("hardware-test-component") {
+# component_name = "sdmmc-hardware-test"
+# manifest = "hardware-test.cml"
+# deps = [ ":hardware-test-bin" ]
+# }
+
+fuchsia_shell_package("hardware-test") {
+ package_name = "sdmmc-hardware-test"
+ deps = [ ":hardware-test-bin" ]
+}
diff --git a/src/devices/block/drivers/sdmmc/hardware-test/hardware-test.cc b/src/devices/block/drivers/sdmmc/hardware-test/hardware-test.cc
new file mode 100644
index 0000000..ada5e3d
--- /dev/null
+++ b/src/devices/block/drivers/sdmmc/hardware-test/hardware-test.cc
@@ -0,0 +1,341 @@
+// 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 <dirent.h>
+#include <fidl/fuchsia.device/cpp/wire.h>
+#include <fidl/fuchsia.hardware.i2c/cpp/wire.h>
+#include <fidl/fuchsia.hardware.sdio/cpp/wire.h>
+#include <fuchsia/hardware/sdio/c/banjo.h>
+#include <lib/fdio/fdio.h>
+#include <lib/fidl/llcpp/arena.h>
+#include <lib/zx/clock.h>
+#include <lib/zx/time.h>
+#include <stdio.h>
+#include <sys/types.h>
+
+#include <vector>
+
+#include <fbl/unique_fd.h>
+#include <zxtest/zxtest.h>
+
+#include "sdmmc-test-device-controller-regs.h"
+#include "sdmmc-test-device-controller.h"
+
+namespace sdmmc {
+
+class SdmmcHardwareTest : public zxtest::Test {
+ public:
+ void SetUp() override {
+ fidl::WireSyncClient i2c =
+ GetFidlClient<fuchsia_hardware_i2c::Device2>(kControllerI2cDevicePath);
+ controller_ = SdmmcTestDeviceController(std::move(i2c));
+ ASSERT_TRUE(controller_.is_valid(), "Failed to connect to I2C device");
+
+ // Reset the test device controller. This will probably fail because the controller doesn't ack
+ // on I2C before resetting.
+ CoreControl::Get().FromValue(0).set_por_reset(1).WriteTo(controller_);
+
+ const zx::time start = zx::clock::get_monotonic();
+ while (CoreControl::Get().FromValue(0).ReadFrom(controller_) != ZX_OK) {
+ }
+ printf("Took %luus for core to reset\n", (zx::clock::get_monotonic() - start).to_usecs());
+
+ EXPECT_OK(CoreControl::Get().FromValue(0).set_core_enable(1).WriteTo(controller_));
+
+ // 0x0000 is reserved, set RCA to 0x0001 instead.
+ EXPECT_OK(Rca0::Get().FromValue(1).WriteTo(controller_));
+ EXPECT_OK(Rca1::Get().FromValue(0).WriteTo(controller_));
+
+ // Support the entire voltage range.
+ EXPECT_OK(Ocr0::Get().FromValue(0).WriteTo(controller_));
+ EXPECT_OK(Ocr1::Get().FromValue(0b1111'1111).WriteTo(controller_));
+ EXPECT_OK(Ocr2::Get().FromValue(0b1111'1111).WriteTo(controller_));
+
+ SetupCis();
+ }
+
+ protected:
+ // TODO: Extract these into a device-specific config
+ static constexpr char kControllerI2cDevicePath[] =
+ "/dev/sys/platform/05:00:2/aml-i2c/i2c/i2c-1-50";
+ static constexpr char kSdmmcDevicePath[] = "/dev/aml_sd";
+
+ void SetupCis() {
+ // CCCR
+ EXPECT_OK(controller_.WriteFunction(
+ 0, 0x0000,
+ {
+ // clang-format disable
+ /* [000] */ 0x43, 0x03, 0x00, 0x06, // SDIO spec 3.00, functions 1 and 2 ready
+ /* [004] */ 0x00, 0x00, 0x00, 0x00,
+ // ----- BUG ----- Must set 4BLS for core driver to register four-bit bus capability.
+ /* [008] */ 0x83, 0x00, 0x10, 0x00, // cmd52/cmd53 supported, CIS pointer (0x1000)
+ /* [00c] */ 0x00, 0x00, 0x00, 0x00,
+ /* [010] */ 0x00, 0x00, 0x00, 0x01, // No function 0 block ops, high speed supported
+ /* [014] */ 0x07, 0x00, 0x00, 0x00, // SDR50/SDR104/DDR50 supported
+ // clang-format enable
+ }));
+
+ // FBR function 1
+ EXPECT_OK(controller_.WriteFunction(
+ 0, 0x0100,
+ {
+ // clang-format disable
+ /* [100] */ 0x00, 0x00, 0x00, 0x00,
+ /* [104] */ 0x00, 0x00, 0x00, 0x00,
+ /* [108] */ 0x00, 0x0d, 0x10, 0x00, // CIS pointer (0x100d)
+ /* [10c] */ 0x00, 0x00, 0x00, 0x00,
+ /* [110] */ 0x00, 0x00, 0x00, 0x00, // Block size set to zero initially
+ // clang-format enable
+ }));
+
+ // FBR function 2
+ EXPECT_OK(controller_.WriteFunction(
+ 0, 0x0200,
+ {
+ // clang-format disable
+ /* [200] */ 0x00, 0x00, 0x00, 0x00,
+ /* [204] */ 0x00, 0x00, 0x00, 0x00,
+ /* [208] */ 0x00, 0x0d, 0x10, 0x00, // CIS pointer (0x100d)
+ /* [20c] */ 0x00, 0x00, 0x00, 0x00,
+ /* [210] */ 0x00, 0x00, 0x00, 0x00, // Block size set to zero initially
+ // clang-format enable
+ }));
+
+ EXPECT_OK(controller_.WriteFunction(
+ 0, 0x1000,
+ {
+ // clang-format disable
+ // CISTPL_FUNCE for function 0: 4 bytes, block size 256, max transfer rate 200Mbit/s.
+ 0x22, 0x04, 0x00, 0x00, 0x01, 0b101'011,
+ // CISTPL_MANFID for function 0: 4 bytes, manufacturer ID 0x0000, product ID 0x0000.
+ 0x20, 0x04, 0x00, 0x00, 0x00, 0x00,
+ // CISTPL_END
+ 0xff,
+ // CISTPL_FUNCE for I/O functions: 42 bytes, block size 512.
+ 0x22, 0x2a, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00,
+ // CISTPL_MANFID for function 0: 4 bytes, manufacturer ID 0x0000, product ID 0x0000.
+ 0x20, 0x04, 0x00, 0x00, 0x00, 0x00,
+ // CISTPL_END
+ 0xff,
+ // clang-format enable
+ }));
+ }
+
+ static void RestartSdmmcDriver() {
+ fidl::WireSyncClient sdmmc_device = GetFidlClient<fuchsia_device::Controller>(kSdmmcDevicePath);
+ ASSERT_TRUE(sdmmc_device.is_valid());
+
+ const auto response = sdmmc_device->Rebind(fidl::StringView());
+ ASSERT_TRUE(response.ok());
+ EXPECT_TRUE(response->result.is_response());
+ }
+
+ std::vector<fidl::WireSyncClient<fuchsia_hardware_sdio::Device>> GetTestSdioFidlClients(
+ const uint8_t max_function) {
+ if (max_function == 0 || max_function > 7) {
+ return {};
+ }
+
+ std::vector<fidl::WireSyncClient<fuchsia_hardware_sdio::Device>> clients(max_function);
+ for (uint8_t i = 1; i <= max_function; i++) {
+ // Loop indefinitely trying to get the client. We have to wait for SDIO intialization to
+ // complete for the devices to be created, which may take some time. If initialization errors
+ // occur let the test runner time out and fail the test.
+ while (!clients[i - 1].is_valid()) {
+ clients[i - 1] = GetTestSdioFidlClient(i);
+ }
+ }
+
+ return clients;
+ }
+
+ SdmmcTestDeviceController controller_;
+
+ private:
+ // Attempts to get a client of the specified function device for the SDIO test rig. Returns an
+ // invalid client on any errors.
+ static fidl::WireSyncClient<fuchsia_hardware_sdio::Device> GetTestSdioFidlClient(
+ const uint8_t function) {
+#if 0
+ constexpr uint32_t kTestManufacturerId = 0x02d0;
+ constexpr uint32_t kTestProductId = 0x4359;
+#endif
+ constexpr uint32_t kTestManufacturerId = 0x0000;
+ constexpr uint32_t kTestProductId = 0x0000;
+
+ if (function == 0) {
+ return {};
+ }
+
+ DIR* const sdio_dir = opendir("/dev/class/sdio");
+ if (sdio_dir == nullptr) {
+ return {};
+ }
+
+ fidl::WireSyncClient<fuchsia_hardware_sdio::Device> function_client = {};
+ struct dirent* entry = readdir(sdio_dir);
+ for (; entry != nullptr; entry = readdir(sdio_dir)) {
+ fbl::unique_fd device(openat(dirfd(sdio_dir), entry->d_name, O_RDWR));
+ if (!device.is_valid()) {
+ continue;
+ }
+
+ fidl::WireSyncClient client = GetFidlClient<fuchsia_hardware_sdio::Device>(std::move(device));
+ if (!client.is_valid()) {
+ continue;
+ }
+
+ const auto response = client->GetDevHwInfo();
+ if (response->result.is_err()) {
+ continue;
+ }
+ if (response->result.response().function != function) {
+ continue;
+ }
+
+ const auto& hw_info = response->result.response().hw_info;
+ if (hw_info.dev_hw_info.num_funcs <= function) {
+ continue;
+ }
+
+ const auto& function_info = hw_info.funcs_hw_info[function];
+ if (function_info.manufacturer_id == kTestManufacturerId &&
+ function_info.product_id == kTestProductId) {
+ function_client = std::move(client);
+ break;
+ }
+ }
+
+ closedir(sdio_dir);
+ return function_client;
+ }
+};
+
+TEST_F(SdmmcHardwareTest, InitSuccess) {
+ // Re-bind the SDMMC driver to initialize with the new settings.
+ printf("Restarting SDMMC driver\n");
+ RestartSdmmcDriver();
+ printf("Done, waiting for FPGA init success\n");
+
+ EXPECT_OK(CoreStatus::WaitForInitSuccess(controller_));
+ printf("Done, waiting for SDIO clients\n");
+
+ auto clients = GetTestSdioFidlClients(2);
+ ASSERT_EQ(clients.size(), 2);
+ printf("Done\n");
+
+ // ----- BUG ----- Core driver doesn't set SDIO_CARD_DIRECT_COMMAND capability.
+ constexpr uint32_t kExpectedSdioCaps =
+ SDIO_CARD_MULTI_BLOCK | /* SDIO_CARD_DIRECT_COMMAND | */ SDIO_CARD_HIGH_SPEED |
+ SDIO_CARD_FOUR_BIT_BUS | SDIO_CARD_UHS_SDR50 | SDIO_CARD_UHS_SDR104 | SDIO_CARD_UHS_DDR50;
+
+ for (const auto& client : clients) {
+ // TODO: Read CCCR and make sure all registers were set correctly
+ const auto response = client->GetDevHwInfo();
+ ASSERT_TRUE(response.ok());
+ ASSERT_TRUE(response->result.is_response());
+
+ const auto& hw_info = response->result.response().hw_info;
+ EXPECT_EQ(hw_info.dev_hw_info.num_funcs, 3); // Includes function 0
+ EXPECT_EQ(hw_info.dev_hw_info.sdio_vsn, 4); // Version 3.00
+ EXPECT_EQ(hw_info.dev_hw_info.cccr_vsn, 3); // Version 3.00
+ EXPECT_EQ(hw_info.dev_hw_info.caps, kExpectedSdioCaps);
+
+ for (uint32_t i = 0; i < 3; i++) {
+ EXPECT_EQ(hw_info.funcs_hw_info[i].manufacturer_id, 0);
+ EXPECT_EQ(hw_info.funcs_hw_info[i].product_id, 0);
+ if (i == 0) {
+ EXPECT_EQ(hw_info.funcs_hw_info[i].max_blk_size, 256);
+ EXPECT_EQ(hw_info.funcs_hw_info[i].max_tran_speed, 200'000);
+ } else {
+ EXPECT_EQ(hw_info.funcs_hw_info[i].max_blk_size, 512);
+ }
+ EXPECT_EQ(hw_info.funcs_hw_info[i].fn_intf_code, 0);
+ }
+ }
+}
+
+TEST_F(SdmmcHardwareTest, InitSuccessWithCmd52Retries) {
+ auto control = CoreControl::Get().FromValue(0);
+ EXPECT_OK(control.ReadFrom(controller_));
+ EXPECT_OK(control.set_error_injection_enable(1).WriteTo(controller_));
+
+ EXPECT_OK(CrcErrorControl::Get().FromValue(0).set_cmd52_crc_error_enable(1).WriteTo(controller_));
+ EXPECT_OK(
+ Cmd52ErrorControl::Get().FromValue(0).set_transfers_until_crc_error(5).WriteTo(controller_));
+
+ // Re-bind the SDMMC driver to initialize with the new settings.
+ printf("Restarting SDMMC driver\n");
+ RestartSdmmcDriver();
+ printf("Done, waiting for FPGA init success\n");
+
+ EXPECT_OK(CoreStatus::WaitForInitSuccess(controller_));
+ printf("Done, waiting for SDIO clients\n");
+
+ auto clients = GetTestSdioFidlClients(2);
+ ASSERT_EQ(clients.size(), 2);
+ printf("Done\n");
+}
+
+TEST_F(SdmmcHardwareTest, InitFailureCmd52Errors) {
+ auto control = CoreControl::Get().FromValue(0);
+ EXPECT_OK(control.ReadFrom(controller_));
+ EXPECT_OK(control.set_error_injection_enable(1).WriteTo(controller_));
+
+ EXPECT_OK(CrcErrorControl::Get().FromValue(0).set_cmd52_crc_error_enable(1).WriteTo(controller_));
+ EXPECT_OK(Cmd52ErrorControl::Get().FromValue(0).WriteTo(controller_));
+
+ // Re-bind the SDMMC driver to initialize with the new settings.
+ printf("Restarting SDMMC driver\n");
+ RestartSdmmcDriver();
+ printf("Done, waiting for FPGA init failure\n");
+
+ EXPECT_OK(CoreStatus::WaitForInitFailure(controller_));
+ printf("Done\n");
+}
+
+TEST_F(SdmmcHardwareTest, ReadCccr) {
+ // Re-bind the SDMMC driver to initialize with the new settings.
+ printf("Restarting SDMMC driver\n");
+ RestartSdmmcDriver();
+ printf("Done, waiting for FPGA init success\n");
+
+ EXPECT_OK(CoreStatus::WaitForInitSuccess(controller_));
+ printf("Done, waiting for SDIO clients\n");
+
+ auto clients = GetTestSdioFidlClients(2);
+ ASSERT_EQ(clients.size(), 2);
+ printf("Done\n");
+
+ zx::status<uint8_t> io_enable = controller_.ReadFunction(0, 0x2);
+ ASSERT_TRUE(io_enable.is_ok());
+ EXPECT_EQ(io_enable.value(), 0b110); // Functions 1 and 2 should be enabled.
+}
+
+TEST_F(SdmmcHardwareTest, ReadWriteCccr) {
+ // Write a reserved bus width value, and make sure it changes after init.
+ EXPECT_OK(controller_.WriteFunction(0, 0x7, {0b01}));
+
+ // Re-bind the SDMMC driver to initialize with the new settings.
+ printf("Restarting SDMMC driver\n");
+ RestartSdmmcDriver();
+ printf("Done, waiting for FPGA init success\n");
+
+ EXPECT_OK(CoreStatus::WaitForInitSuccess(controller_));
+ printf("Done, waiting for SDIO clients\n");
+
+ auto clients = GetTestSdioFidlClients(2);
+ ASSERT_EQ(clients.size(), 2);
+ printf("Done\n");
+
+ zx::status<uint8_t> bus_width = controller_.ReadFunction(0, 0x7);
+ ASSERT_TRUE(bus_width.is_ok());
+ EXPECT_EQ(bus_width.value(), 0b10);
+}
+
+} // namespace sdmmc
diff --git a/src/devices/block/drivers/sdmmc/hardware-test/hardware-test.cml b/src/devices/block/drivers/sdmmc/hardware-test/hardware-test.cml
new file mode 100644
index 0000000..cd9709e
--- /dev/null
+++ b/src/devices/block/drivers/sdmmc/hardware-test/hardware-test.cml
@@ -0,0 +1,27 @@
+// 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: [
+ "//sdk/lib/syslog/client.shard.cml",
+ "//src/sys/test_manager/system-test.shard.cml",
+ "//src/sys/test_runners/gtest/default.shard.cml",
+ ],
+ program: {
+ binary: "bin/sdmmc_hardware_test",
+ },
+ use: [
+ {
+ protocol: [
+ "fuchsia.hardware.i2c.Device2",
+ "fuchsia.sysinfo.SysInfo",
+ ],
+ },
+ {
+ directory: "dev-mediacodec",
+ from: "parent",
+ rights: [ "rw*" ],
+ path: "/dev/class/media-codec",
+ },
+ ],
+}
diff --git a/src/devices/block/drivers/sdmmc/hardware-test/main.cc b/src/devices/block/drivers/sdmmc/hardware-test/main.cc
new file mode 100644
index 0000000..10c1cc7
--- /dev/null
+++ b/src/devices/block/drivers/sdmmc/hardware-test/main.cc
@@ -0,0 +1,110 @@
+// 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 <fidl/fuchsia.hardware.gpio/cpp/wire.h>
+#include <fidl/fuchsia.hardware.i2c/cpp/wire.h>
+#include <fidl/fuchsia.sysinfo/cpp/wire.h>
+#include <stdio.h>
+
+#include <zxtest/zxtest.h>
+
+#include "sdmmc-test-device-controller.h"
+
+static zx_status_t CheckControllerIdAndVersion() {
+ // TODO: Extract these into a device-specific config
+ constexpr char kControllerI2cDevicePath[] = "/dev/sys/platform/05:00:2/aml-i2c/i2c/i2c-1-50";
+ constexpr uint8_t kExpectedCoreVersion = 1;
+ constexpr char kExpectedControllerId[] = "SDIO";
+
+ fidl::WireSyncClient i2c =
+ sdmmc::GetFidlClient<fuchsia_hardware_i2c::Device2>(kControllerI2cDevicePath);
+ sdmmc::SdmmcTestDeviceController controller(std::move(i2c));
+ if (!controller.is_valid()) {
+ fprintf(stderr, "Failed to connect to %s\n", kControllerI2cDevicePath);
+ return ZX_ERR_IO;
+ }
+
+ // TODO: Re-enable these checks
+#if 1
+ const auto id = controller.GetId();
+ if (id.is_error()) {
+ fprintf(stderr, "Failed to read controller ID: %s\n", id.status_string());
+ return id.error_value();
+ }
+
+ if (id->size() != strlen(kExpectedControllerId) ||
+ memcmp(id->data(), kExpectedControllerId, id->size()) != 0) {
+ fprintf(stderr, "Invalid controller ID\n");
+ return ZX_ERR_BAD_STATE;
+ }
+
+ const auto version = controller.GetCoreVersion();
+ if (version.is_error()) {
+ fprintf(stderr, "Failed to read controller version: %s\n", id.status_string());
+ return version.error_value();
+ }
+
+ if (version.value() != kExpectedCoreVersion) {
+ fprintf(stderr, "Unexpected core version %u\n", version.value());
+ return ZX_ERR_BAD_STATE;
+ }
+#endif
+
+ return ZX_OK;
+}
+
+int main(int argc, char** argv) {
+ // TODO: Extract these into a device-specific config
+ constexpr char kSysInfoPath[] = "/svc/fuchsia.sysinfo.SysInfo";
+ constexpr char kExpectedBoardName[] = "vim3";
+
+ constexpr char kPowerGpioDevicePath[] = "/dev/gpio-expander/ti-tca6408a/gpio-107";
+
+ setlinebuf(stdout);
+
+ fidl::WireSyncClient sysinfo = sdmmc::GetFidlClient<fuchsia_sysinfo::SysInfo>(kSysInfoPath);
+ if (!sysinfo.is_valid()) {
+ fprintf(stderr, "Failed to connect to %s\n", kSysInfoPath);
+ return 1;
+ }
+
+ const auto board_name = sysinfo->GetBoardName();
+ if (!board_name.ok() || board_name->status != ZX_OK) {
+ fprintf(stderr, "Failed to get board name\n");
+ return 1;
+ }
+
+ if (board_name->name.size() != strlen(kExpectedBoardName) ||
+ memcmp(board_name->name.data(), kExpectedBoardName, board_name->name.size()) != 0) {
+ printf("Detected unsupported board %s, skipping tests\n", kExpectedBoardName);
+ return 0;
+ }
+
+ if (CheckControllerIdAndVersion() != ZX_OK) {
+ return 1;
+ }
+
+ fidl::WireSyncClient voltage_gpio =
+ sdmmc::GetFidlClient<fuchsia_hardware_gpio::Gpio>(kPowerGpioDevicePath);
+ if (!voltage_gpio.is_valid()) {
+ fprintf(stderr, "Failed to connect to %s\n", kPowerGpioDevicePath);
+ return 1;
+ }
+
+ // Set the bus voltage to 1.8V.
+ const auto response = voltage_gpio->ConfigOut(1);
+ if (!response.ok() || response->result.is_err()) {
+ fprintf(stderr, "Failed to set SDMMC bus voltage\n");
+ return 1;
+ }
+
+ // Sleep for some time to allow the voltage to stabilize.
+ zx::nanosleep(zx::deadline_after(zx::sec(1)));
+
+ const int ret = RUN_ALL_TESTS(argc, argv);
+
+ // Set the bus voltage back to 3.3V.
+ voltage_gpio->ConfigOut(0);
+ return ret;
+}
diff --git a/src/devices/block/drivers/sdmmc/hardware-test/sdmmc-test-device-controller-regs.h b/src/devices/block/drivers/sdmmc/hardware-test/sdmmc-test-device-controller-regs.h
new file mode 100644
index 0000000..df8826e
--- /dev/null
+++ b/src/devices/block/drivers/sdmmc/hardware-test/sdmmc-test-device-controller-regs.h
@@ -0,0 +1,153 @@
+// 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.
+
+#ifndef SRC_DEVICES_BLOCK_DRIVERS_SDMMC_HARDWARE_TEST_SDMMC_TEST_DEVICE_CONTROLLER_REGS_H_
+#define SRC_DEVICES_BLOCK_DRIVERS_SDMMC_HARDWARE_TEST_SDMMC_TEST_DEVICE_CONTROLLER_REGS_H_
+
+#include <hwreg/bitfields.h>
+
+#include "sdmmc-test-device-controller.h"
+
+namespace sdmmc {
+
+// See //zircon/system/ulib/hwreg-i2c/include/hwreg/i2c.h
+
+template <class DerivedType>
+class SdmmcTestDeviceControllerRegisterBase
+ : public hwreg::RegisterBase<DerivedType, uint8_t, void> {
+ public:
+ template <typename T>
+ DerivedType& ReadFrom(T* reg_io) = delete;
+ template <typename T>
+ DerivedType& WriteTo(T* mmio) = delete;
+
+ using RegisterBaseType = hwreg::RegisterBase<DerivedType, uint8_t, void>;
+
+ zx_status_t ReadFrom(SdmmcTestDeviceController& controller) {
+ const auto addr = static_cast<uint8_t>(RegisterBaseType::reg_addr());
+
+ zx::status<uint8_t> value = controller.ReadReg(addr);
+ if (value.is_error()) {
+ return value.error_value();
+ }
+ RegisterBaseType::set_reg_value(value.value());
+ return ZX_OK;
+ }
+
+ zx_status_t WriteTo(SdmmcTestDeviceController& controller) {
+ const auto addr = static_cast<uint8_t>(RegisterBaseType::reg_addr());
+ return controller.WriteReg(addr, RegisterBaseType::reg_value()).status_value();
+ }
+};
+
+template <class RegType>
+class SdmmcTestDeviceControllerRegisterAddr : public hwreg::RegisterAddr<RegType> {
+ public:
+ static_assert(std::is_base_of<SdmmcTestDeviceControllerRegisterBase<RegType>, RegType>::value,
+ "Parameter of SdmmcTestDeviceControllerRegisterAddr<> should derive from "
+ "SdmmcTestDeviceControllerRegisterBase");
+
+ template <typename T>
+ RegType ReadFrom(T* reg_io) = delete;
+
+ explicit SdmmcTestDeviceControllerRegisterAddr(uint8_t reg_addr)
+ : hwreg::RegisterAddr<RegType>(reg_addr) {}
+};
+
+class CoreControl : public SdmmcTestDeviceControllerRegisterBase<CoreControl> {
+ public:
+ static auto Get() { return SdmmcTestDeviceControllerRegisterAddr<CoreControl>(0x5); }
+
+ DEF_BIT(0, core_enable);
+ DEF_BIT(1, error_injection_enable);
+ DEF_BIT(7, por_reset);
+};
+
+class CoreStatus : public SdmmcTestDeviceControllerRegisterBase<CoreStatus> {
+ public:
+ static auto Get() { return SdmmcTestDeviceControllerRegisterAddr<CoreStatus>(0x6); }
+
+ static zx_status_t WaitForInitSuccess(SdmmcTestDeviceController& controller) {
+ CoreStatus reg = CoreStatus::Get().FromValue(0);
+ while (!reg.init_finished()) {
+ if (zx_status_t status = reg.ReadFrom(controller); status != ZX_OK) {
+ return status;
+ }
+ }
+
+ return ZX_OK;
+ }
+
+ static zx_status_t WaitForInitFailure(SdmmcTestDeviceController& controller) {
+ CoreStatus reg = CoreStatus::Get().FromValue(0);
+ while (!reg.init_failed()) {
+ if (zx_status_t status = reg.ReadFrom(controller); status != ZX_OK) {
+ return status;
+ }
+ }
+
+ return ZX_OK;
+ }
+
+ DEF_BIT(0, init_finished);
+ DEF_BIT(1, init_failed);
+};
+
+class Ocr2 : public SdmmcTestDeviceControllerRegisterBase<Ocr2> {
+ public:
+ static auto Get() { return SdmmcTestDeviceControllerRegisterAddr<Ocr2>(0x7); }
+};
+
+class Ocr1 : public SdmmcTestDeviceControllerRegisterBase<Ocr1> {
+ public:
+ static auto Get() { return SdmmcTestDeviceControllerRegisterAddr<Ocr1>(0x8); }
+};
+
+class Ocr0 : public SdmmcTestDeviceControllerRegisterBase<Ocr0> {
+ public:
+ static auto Get() { return SdmmcTestDeviceControllerRegisterAddr<Ocr0>(0x9); }
+};
+
+class Rca1 : public SdmmcTestDeviceControllerRegisterBase<Rca1> {
+ public:
+ static auto Get() { return SdmmcTestDeviceControllerRegisterAddr<Rca1>(0xa); }
+};
+
+class Rca0 : public SdmmcTestDeviceControllerRegisterBase<Rca0> {
+ public:
+ static auto Get() { return SdmmcTestDeviceControllerRegisterAddr<Rca0>(0xb); }
+};
+
+class CardStatusR1 : public SdmmcTestDeviceControllerRegisterBase<CardStatusR1> {
+ public:
+ static auto Get() { return SdmmcTestDeviceControllerRegisterAddr<CardStatusR1>(0xc); }
+
+ DEF_BIT(0, error);
+ DEF_BIT(1, illegal_command);
+ DEF_BIT(2, com_crc_error);
+ DEF_BIT(3, out_of_range);
+};
+
+class CardStatusR5 : public SdmmcTestDeviceControllerRegisterBase<CardStatusR5> {
+ public:
+ static auto Get() { return SdmmcTestDeviceControllerRegisterAddr<CardStatusR5>(0xd); }
+};
+
+class CrcErrorControl : public SdmmcTestDeviceControllerRegisterBase<CrcErrorControl> {
+ public:
+ static auto Get() { return SdmmcTestDeviceControllerRegisterAddr<CrcErrorControl>(0xe); }
+
+ DEF_BIT(6, cmd52_crc_error_enable);
+};
+
+class Cmd52ErrorControl : public SdmmcTestDeviceControllerRegisterBase<Cmd52ErrorControl> {
+ public:
+ static auto Get() { return SdmmcTestDeviceControllerRegisterAddr<Cmd52ErrorControl>(0x17); }
+
+ DEF_FIELD(3, 0, transfers_until_crc_error);
+};
+
+} // namespace sdmmc
+
+#endif // SRC_DEVICES_BLOCK_DRIVERS_SDMMC_HARDWARE_TEST_SDMMC_TEST_DEVICE_CONTROLLER_REGS_H_
diff --git a/src/devices/block/drivers/sdmmc/hardware-test/sdmmc-test-device-controller.cc b/src/devices/block/drivers/sdmmc/hardware-test/sdmmc-test-device-controller.cc
new file mode 100644
index 0000000..22a2fab
--- /dev/null
+++ b/src/devices/block/drivers/sdmmc/hardware-test/sdmmc-test-device-controller.cc
@@ -0,0 +1,177 @@
+// 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 "sdmmc-test-device-controller.h"
+
+namespace {
+
+constexpr int kI2cRetries = 10;
+
+} // namespace
+
+namespace sdmmc {
+
+zx::status<std::vector<uint8_t>> SdmmcTestDeviceController::ReadI2c(
+ const std::vector<uint8_t>& address, const uint8_t size) {
+ fidl::Arena allocator;
+
+ fidl::WireRequest<fuchsia_hardware_i2c::Device2::Transfer> request;
+
+ request.segments_is_write = fidl::VectorView<bool>(allocator, 2);
+ request.segments_is_write[0] = true;
+ request.segments_is_write[1] = false;
+
+ request.write_segments_data = fidl::VectorView<fidl::VectorView<uint8_t>>(allocator, 1);
+ request.write_segments_data[0] = fidl::VectorView<uint8_t>(allocator, address.size());
+ memcpy(request.write_segments_data[0].mutable_data(), address.data(),
+ address.size() * sizeof(address[0]));
+
+ request.read_segments_length = fidl::VectorView<uint8_t>(allocator, 1);
+ request.read_segments_length[0] = size;
+
+ return RetryI2cRequest(request);
+}
+
+zx::status<> SdmmcTestDeviceController::WriteI2c(const std::vector<uint8_t>& address,
+ const std::vector<uint8_t>& data) {
+ if (address.size() + data.size() > UINT8_MAX) {
+ return zx::error(ZX_ERR_OUT_OF_RANGE);
+ }
+
+ fidl::Arena allocator;
+
+ fidl::WireRequest<fuchsia_hardware_i2c::Device2::Transfer> request;
+
+ request.segments_is_write = fidl::VectorView<bool>(allocator, 1);
+ request.segments_is_write[0] = true;
+
+ request.write_segments_data = fidl::VectorView<fidl::VectorView<uint8_t>>(allocator, 1);
+ request.write_segments_data[0] =
+ fidl::VectorView<uint8_t>(allocator, address.size() + data.size());
+
+ {
+ uint8_t* const write_data = request.write_segments_data[0].mutable_data();
+ memcpy(write_data, address.data(), address.size() * sizeof(address[0]));
+ memcpy(write_data + address.size(), data.data(), data.size() * sizeof(data[0]));
+ }
+
+ request.read_segments_length = fidl::VectorView<uint8_t>(allocator, 0);
+
+ if (const auto status = RetryI2cRequest(request); status.is_error()) {
+ return zx::error(status.error_value());
+ }
+ return zx::ok();
+}
+
+zx::status<std::vector<uint8_t>> SdmmcTestDeviceController::ReadReg(const uint8_t reg,
+ const uint8_t size) {
+ return ReadI2c({reg}, size);
+}
+
+zx::status<uint8_t> SdmmcTestDeviceController::ReadReg(const uint8_t reg) {
+ const zx::status<std::vector<uint8_t>> read_data = ReadI2c({reg}, 1);
+ if (read_data.is_error()) {
+ return zx::error(read_data.error_value());
+ }
+ return zx::ok((*read_data)[0]);
+}
+
+zx::status<> SdmmcTestDeviceController::WriteReg(const uint8_t reg, const uint8_t value) {
+ return WriteI2c({reg}, {value});
+}
+
+zx::status<std::vector<uint8_t>> SdmmcTestDeviceController::ReadFunction(const uint8_t function,
+ const uint32_t address,
+ const uint8_t size) {
+ if (address > kMaxFunctionAddress || function > 7) {
+ return zx::error(ZX_ERR_OUT_OF_RANGE);
+ }
+ return ReadI2c(FunctionAddressToVector(function, address), size);
+}
+
+zx::status<uint8_t> SdmmcTestDeviceController::ReadFunction(const uint8_t function,
+ const uint32_t address) {
+ const zx::status<std::vector<uint8_t>> read_data = ReadFunction(function, address, 1);
+ if (read_data.is_error()) {
+ return zx::error(read_data.error_value());
+ }
+ return zx::ok((*read_data)[0]);
+}
+
+zx::status<> SdmmcTestDeviceController::WriteFunction(const uint8_t function,
+ const uint32_t address,
+ const std::vector<uint8_t>& data) {
+ if (address > kMaxFunctionAddress || function > 7) {
+ return zx::error(ZX_ERR_OUT_OF_RANGE);
+ }
+ return WriteI2c(FunctionAddressToVector(function, address), data);
+}
+
+zx::status<std::array<uint8_t, 4>> SdmmcTestDeviceController::GetId() {
+ std::array<uint8_t, 4> id;
+ // TODO: Register address constant
+ zx::status<std::vector<uint8_t>> id_vector = ReadReg(1, id.size());
+ if (id_vector.is_error()) {
+ return zx::error(id_vector.status_value());
+ }
+ if (id_vector->size() != id.size()) {
+ fprintf(stderr, "Unexpected read data size %lu\n", id_vector->size());
+ return zx::error(ZX_ERR_INTERNAL);
+ }
+
+ memcpy(id.data(), id_vector->data(), id.size());
+ return zx::ok(id);
+}
+
+std::vector<uint8_t> SdmmcTestDeviceController::FunctionAddressToVector(const uint8_t function,
+ const uint32_t address) {
+ std::vector<uint8_t> ret(4);
+ ret[0] = 0xf0 | function;
+ ret[1] = (address >> 16) & 0xff;
+ ret[2] = (address >> 8) & 0xff;
+ ret[3] = address & 0xff;
+ return ret;
+}
+
+zx::status<std::vector<uint8_t>> SdmmcTestDeviceController::RetryI2cRequest(
+ const fidl::WireRequest<fuchsia_hardware_i2c::Device2::Transfer>& request) {
+ for (int i = 0; i < kI2cRetries; i++) {
+ fidl::WireRequest<fuchsia_hardware_i2c::Device2::Transfer> req = request;
+ const auto response =
+ fidl::WireResult<fuchsia_hardware_i2c::Device2::Transfer>(i2c_.client_end(), &req);
+ if (!response.ok()) {
+ fprintf(stderr, "FIDL request failed: %s\n", zx_status_get_string(response.status()));
+ return zx::error(response.status());
+ }
+
+ // An error here represents an I2C bus error, continue to retry the transfer.
+ if (response->result.is_err()) {
+ continue;
+ }
+
+ if (response->result.response().read_segments_data.count() !=
+ request.read_segments_length.count()) {
+ fprintf(stderr, "Invalid read segments count %lu\n",
+ response->result.response().read_segments_data.count());
+ return zx::error(ZX_ERR_INTERNAL);
+ }
+
+ // Write request -- no data to return.
+ if (request.read_segments_length.count() == 0) {
+ return zx::ok(std::vector<uint8_t>{});
+ }
+
+ const fidl::VectorView<uint8_t>& read_data = response->result.response().read_segments_data[0];
+ if (read_data.count() != request.read_segments_length[0]) {
+ fprintf(stderr, "Unexpected read data size %lu\n", read_data.count());
+ return zx::error(ZX_ERR_INTERNAL);
+ }
+
+ return zx::ok(std::vector<uint8_t>(read_data.data(), read_data.data() + read_data.count()));
+ }
+
+ return zx::error(ZX_ERR_IO);
+}
+
+} // namespace sdmmc
diff --git a/src/devices/block/drivers/sdmmc/hardware-test/sdmmc-test-device-controller.h b/src/devices/block/drivers/sdmmc/hardware-test/sdmmc-test-device-controller.h
new file mode 100644
index 0000000..3b4d588
--- /dev/null
+++ b/src/devices/block/drivers/sdmmc/hardware-test/sdmmc-test-device-controller.h
@@ -0,0 +1,86 @@
+// 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.
+
+#ifndef SRC_DEVICES_BLOCK_DRIVERS_SDMMC_HARDWARE_TEST_SDMMC_TEST_DEVICE_CONTROLLER_H_
+#define SRC_DEVICES_BLOCK_DRIVERS_SDMMC_HARDWARE_TEST_SDMMC_TEST_DEVICE_CONTROLLER_H_
+
+#include <fcntl.h>
+#include <fidl/fuchsia.hardware.i2c/cpp/wire.h>
+#include <lib/fdio/fdio.h>
+#include <lib/zx/status.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <array>
+
+#include <fbl/unique_fd.h>
+
+namespace sdmmc {
+
+template <typename T>
+fidl::WireSyncClient<T> GetFidlClient(fbl::unique_fd device) {
+ fidl::ClientEnd<T> client;
+ zx_status_t status =
+ fdio_get_service_handle(device.release(), client.channel().reset_and_get_address());
+ if (status != ZX_OK) {
+ return {};
+ }
+
+ return fidl::WireSyncClient<T>(std::move(client));
+}
+
+template <typename T>
+fidl::WireSyncClient<T> GetFidlClient(const char* path) {
+ fbl::unique_fd device(open(path, O_RDWR));
+ if (!device.is_valid()) {
+ return {};
+ }
+
+ return GetFidlClient<T>(std::move(device));
+}
+
+class SdmmcTestDeviceController {
+ public:
+ SdmmcTestDeviceController() = default;
+ explicit SdmmcTestDeviceController(fidl::WireSyncClient<fuchsia_hardware_i2c::Device2> i2c)
+ : i2c_(std::move(i2c)) {}
+
+ SdmmcTestDeviceController(SdmmcTestDeviceController&& other) noexcept
+ : i2c_(std::move(other.i2c_)) {}
+ SdmmcTestDeviceController& operator=(SdmmcTestDeviceController&& other) noexcept {
+ i2c_ = std::move(other.i2c_);
+ return *this;
+ }
+
+ bool is_valid() const { return i2c_.is_valid(); }
+
+ zx::status<std::vector<uint8_t>> ReadI2c(const std::vector<uint8_t>& address, uint8_t size);
+ zx::status<> WriteI2c(const std::vector<uint8_t>& address, const std::vector<uint8_t>& data);
+
+ zx::status<std::vector<uint8_t>> ReadReg(uint8_t reg, uint8_t size);
+ zx::status<uint8_t> ReadReg(uint8_t reg);
+ zx::status<> WriteReg(uint8_t reg, uint8_t value);
+
+ zx::status<std::vector<uint8_t>> ReadFunction(uint8_t function, uint32_t address, uint8_t size);
+ zx::status<uint8_t> ReadFunction(uint8_t function, uint32_t address);
+ zx::status<> WriteFunction(uint8_t function, uint32_t address, const std::vector<uint8_t>& data);
+
+ // TODO: Register address constant
+ zx::status<uint8_t> GetCoreVersion() { return ReadReg(0); }
+ zx::status<std::array<uint8_t, 4>> GetId();
+
+ private:
+ static constexpr uint32_t kMaxFunctionAddress = 0x1'ffff;
+
+ static std::vector<uint8_t> FunctionAddressToVector(uint8_t function, uint32_t address);
+
+ zx::status<std::vector<uint8_t>> RetryI2cRequest(
+ const fidl::WireRequest<fuchsia_hardware_i2c::Device2::Transfer>& request);
+
+ fidl::WireSyncClient<fuchsia_hardware_i2c::Device2> i2c_;
+};
+
+} // namespace sdmmc
+
+#endif // SRC_DEVICES_BLOCK_DRIVERS_SDMMC_HARDWARE_TEST_SDMMC_TEST_DEVICE_CONTROLLER_H_