blob: ac1c463243546bd4cc2a96305ebcafe06f75990a [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 "sdio.h"
#include <lib/zx/clock.h>
#include <fbl/algorithm.h>
namespace sdio {
using SdioClient = ::llcpp::fuchsia::hardware::sdio::Device::SyncClient;
using namespace ::llcpp::fuchsia::hardware::sdio;
constexpr char kUsageMessage[] = R"""(Usage: sdio <device> <command> [options]
--help - Show this message
--version - Show the version of this tool
info - Display information about the host controller and the card
read-byte <address> - Read one byte from the SDIO function
write-byte <address> <byte> - Write one byte to the SDIO function
read-stress <address> <size> <loops> [--fifo] [--dma] - Read a number of blocks from the SDIO
function and measure the throughput
Example:
sdio /dev/class/sdio/001 read-stress 0x01234 256 100 dma
)""";
constexpr char kVersion[] = "1";
void PrintUsage() { printf("%s", kUsageMessage); }
void PrintVersion() { printf("%s\n", kVersion); }
template <typename T>
bool ParseNumericalArg(const char* const arg, T* const out) {
char* endptr;
unsigned long value = strtoul(arg, &endptr, 0);
if (*endptr != '\0') {
fprintf(stderr, "Failed to parse value: %s\n", arg);
return false;
}
if constexpr (sizeof(T) < sizeof(value)) {
if (value > ((1ul << (sizeof(T) * 8)) - 1)) {
fprintf(stderr, "Value out of range: %s\n", arg);
return false;
}
}
*out = static_cast<T>(value);
return true;
}
std::string GetTxnStats(const zx::duration duration, const uint64_t bytes) {
constexpr size_t kMaxStringSize = 32;
constexpr double kKilobyte = 1000.0;
constexpr double kMegabyte = kKilobyte * 1000.0;
constexpr double kGigabyte = kMegabyte * 1000.0;
char duration_str[kMaxStringSize];
const double duration_nsec = static_cast<double>(duration.to_nsecs());
if (duration >= zx::sec(1)) {
snprintf(duration_str, kMaxStringSize, "%.3f s", duration_nsec / zx::sec(1).to_nsecs());
} else if (duration >= zx::msec(1)) {
snprintf(duration_str, kMaxStringSize, "%.3f ms", duration_nsec / zx::msec(1).to_nsecs());
} else if (duration >= zx::usec(1)) {
snprintf(duration_str, kMaxStringSize, "%.3f us", duration_nsec / zx::usec(1).to_nsecs());
} else {
snprintf(duration_str, kMaxStringSize, "%ld ns", duration.to_nsecs());
}
if (duration.to_nsecs() == 0) {
return std::string(duration_str);
}
char bytes_second_str[kMaxStringSize];
const double bytes_second = static_cast<double>(bytes) / (duration_nsec / zx::sec(1).to_nsecs());
if (bytes_second >= kGigabyte) {
snprintf(bytes_second_str, kMaxStringSize, " (%.3f GB/s)", bytes_second / kGigabyte);
} else if (bytes_second >= kMegabyte) {
snprintf(bytes_second_str, kMaxStringSize, " (%.3f MB/s)", bytes_second / kMegabyte);
} else if (bytes_second >= kKilobyte) {
snprintf(bytes_second_str, kMaxStringSize, " (%.3f kB/s)", bytes_second / kKilobyte);
} else {
snprintf(bytes_second_str, kMaxStringSize, " (%.3f B/s)", bytes_second);
}
return std::string(duration_str) + std::string(bytes_second_str);
}
int Info(SdioClient client) {
struct {
SdioDeviceCapabilities capability;
const char* string;
} constexpr kCapabilityStrings[] = {
{SdioDeviceCapabilities::MULTI_BLOCK, "MULTI_BLOCK"},
{SdioDeviceCapabilities::SRW, "SRW"},
{SdioDeviceCapabilities::DIRECT_COMMAND, "DIRECT_COMMAND"},
{SdioDeviceCapabilities::SUSPEND_RESUME, "SUSPEND_RESUME"},
{SdioDeviceCapabilities::LOW_SPEED, "LOW_SPEED"},
{SdioDeviceCapabilities::HIGH_SPEED, "HIGH_SPEED"},
{SdioDeviceCapabilities::HIGH_POWER, "HIGH_POWER"},
{SdioDeviceCapabilities::FOUR_BIT_BUS, "FOUR_BIT_BUS"},
{SdioDeviceCapabilities::HS_SDR12, "HS_SDR12"},
{SdioDeviceCapabilities::HS_SDR25, "HS_SDR25"},
{SdioDeviceCapabilities::UHS_SDR50, "UHS_SDR50"},
{SdioDeviceCapabilities::UHS_SDR104, "UHS_SDR104"},
{SdioDeviceCapabilities::UHS_DDR50, "UHS_DDR50"},
{SdioDeviceCapabilities::TYPE_A, "TYPE_A"},
{SdioDeviceCapabilities::TYPE_B, "TYPE_B"},
{SdioDeviceCapabilities::TYPE_C, "TYPE_C"},
{SdioDeviceCapabilities::TYPE_D, "TYPE_D"},
};
auto result = client.GetDevHwInfo();
if (!result.ok()) {
fprintf(stderr, "FIDL call GetDevHwInfo failed: %d\n", result.status());
return 1;
}
if (result->result.is_err()) {
fprintf(stderr, "GetDevHwInfo failed: %d\n", result->result.err());
return 1;
}
const SdioHwInfo& info = result->result.response().hw_info;
const SdioDeviceHwInfo& dev_info = info.dev_hw_info;
printf("Host:\n Max transfer size: %u\n", info.host_max_transfer_size);
printf("Card:\n");
printf(
" SDIO version: %u\n"
" CCCR version: %u\n"
" Capabilities: 0x%08x\n",
dev_info.sdio_vsn, dev_info.cccr_vsn, dev_info.caps);
for (size_t i = 0; i < std::size(kCapabilityStrings); i++) {
if (dev_info.caps & static_cast<uint32_t>(kCapabilityStrings[i].capability)) {
printf(" %s\n", kCapabilityStrings[i].string);
}
}
for (uint32_t i = 0; i < dev_info.num_funcs; i++) {
printf(" Function %u:\n", i);
const SdioFuncHwInfo& func_info = info.funcs_hw_info[i];
printf(
" Manufacturer ID: 0x%04x\n"
" Product ID: 0x%04x\n"
" Max block size: %u\n",
func_info.manufacturer_id, func_info.product_id, func_info.max_blk_size);
if (i == 0) {
printf(" Max transfer speed: ");
if (func_info.max_tran_speed > 1000) {
printf("%.1f Mb/s\n", static_cast<double>(func_info.max_tran_speed) / 1000.0);
} else {
printf("%u kb/s\n", func_info.max_tran_speed);
}
} else {
printf(" Interface code: 0x%02x\n", func_info.fn_intf_code);
}
}
return 0;
}
int ReadByte(SdioClient client, uint32_t address, int argc, const char** argv) {
auto result = client.DoRwByte(false, address, 0);
if (!result.ok()) {
fprintf(stderr, "FIDL call DoRwByte failed: %d\n", result.status());
return 1;
}
if (result->result.is_err()) {
fprintf(stderr, "DoRwByte failed: %d\n", result->result.err());
return 1;
}
printf("0x%02x\n", result->result.response().read_byte);
return 0;
}
int WriteByte(SdioClient client, uint32_t address, int argc, const char** argv) {
if (argc < 1) {
fprintf(stderr, "Expected <byte> argument\n");
PrintUsage();
return 1;
}
uint8_t write_value = 0;
if (!ParseNumericalArg(argv[0], &write_value)) {
return 1;
}
auto result = client.DoRwByte(true, address, write_value);
if (!result.ok()) {
fprintf(stderr, "FIDL call DoRwByte failed: %d\n", result.status());
return 1;
}
if (result->result.is_err()) {
fprintf(stderr, "DoRwByte failed: %d\n", result->result.err());
return 1;
}
return 0;
}
int ReadStress(SdioClient client, uint32_t address, int argc, const char** argv) {
if (argc < 2) {
fprintf(stderr, "Expected <size> and <loops> arguments\n");
PrintUsage();
return 1;
}
uint32_t size = 0;
if (!ParseNumericalArg(argv[0], &size)) {
return 1;
}
unsigned long loops = 0;
if (!ParseNumericalArg(argv[1], &loops)) {
return 1;
}
bool incr = true;
bool use_dma = false;
for (int i = 2; i < argc; i++) {
if (strcmp(argv[i], "--fifo") == 0) {
incr = false;
} else if (strcmp(argv[i], "--dma") == 0) {
use_dma = true;
} else {
fprintf(stderr, "Unexpected option: %s\n", argv[i]);
PrintUsage();
return 1;
}
}
std::unique_ptr<uint8_t[]> buffer;
zx::vmo dma_vmo;
if (use_dma) {
zx_status_t status = zx::vmo::create(size, 0, &dma_vmo);
if (status != ZX_OK) {
fprintf(stderr, "Failed to create VMO: %d\n", status);
return 1;
}
} else {
buffer = std::unique_ptr<uint8_t[]>(new uint8_t[size]);
}
const zx::time start = zx::clock::get_monotonic();
for (unsigned long i = 0; i < loops; i++) {
SdioRwTxn txn = {};
txn.addr = address;
txn.data_size = size;
txn.incr = incr;
txn.write = false;
txn.use_dma = use_dma;
txn.buf_offset = 0;
if (use_dma) {
txn.dma_vmo = std::move(dma_vmo);
} else {
txn.virt = fidl::VectorView(fidl::unowned_ptr(buffer.get()), size);
}
auto result = client.DoRwTxn(std::move(txn));
if (!result.ok()) {
fprintf(stderr, "FIDL call DoRwTxn failed: %d\n", result.status());
return 1;
}
if (result->result.is_err()) {
fprintf(stderr, "DoRwTxn failed: %d\n", result->result.err());
return 1;
}
dma_vmo = std::move(result->result.mutable_response().txn.dma_vmo);
}
const zx::duration elapsed = zx::clock::get_monotonic() - start;
const std::string txn_stats = GetTxnStats(elapsed, size * loops);
printf("Read %lu chunks of %u bytes in %s\n", loops, size, txn_stats.c_str());
return 0;
}
int RunSdioTool(SdioClient client, int argc, const char** argv) {
if (argc < 1) {
fprintf(stderr, "Expected <command> argument\n");
PrintUsage();
return 1;
}
const char* const command = argv[0];
if (strcmp(command, "info") == 0) {
return Info(std::move(client));
}
if (argc < 2) {
fprintf(stderr, "Expected <address> argument\n");
PrintUsage();
return 1;
}
const char* const address_str = argv[1];
argc -= 2;
argv += 2;
uint32_t address = 0;
if (!ParseNumericalArg(address_str, &address)) {
return 1;
}
constexpr uint32_t kMaxSdioAddress = (1 << 17) - 1;
if (address > kMaxSdioAddress) {
fprintf(stderr, "Address must be less than 0x%x: %s\n", kMaxSdioAddress, address_str);
return 1;
}
if (strcmp(command, "read-byte") == 0) {
return ReadByte(std::move(client), address, argc, argv);
} else if (strcmp(command, "write-byte") == 0) {
return WriteByte(std::move(client), address, argc, argv);
} else if (strcmp(command, "read-stress") == 0) {
return ReadStress(std::move(client), address, argc, argv);
} else {
fprintf(stderr, "Unexpected command: %s\n", command);
PrintUsage();
return 1;
}
}
} // namespace sdio