portapack-mayhem/firmware/application/ui_sd_card_debug.cpp
2016-05-09 21:53:01 -07:00

402 lines
11 KiB
C++

/*
* Copyright (C) 2016 Jared Boone, ShareBrained Technology, Inc.
*
* This file is part of PortaPack.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#include "ui_sd_card_debug.hpp"
#include "string_format.hpp"
#include "file.hpp"
#include "lfsr_random.hpp"
#include "ff.h"
#include "ch.h"
#include "hal.h"
class SDCardTestThread {
public:
enum Result {
FailCompare = -8,
FailReadIncomplete = -7,
FailWriteIncomplete = -6,
FailAbort = -5,
FailFileOpenRead = -4,
FailFileOpenWrite = -3,
FailHeap = -2,
FailThread = -1,
Incomplete = 0,
OK = 1,
};
struct Stats {
halrtcnt_t write_duration_min { 0 };
halrtcnt_t write_duration_max { 0 };
halrtcnt_t write_test_duration { 0 };
size_t write_bytes { 0 };
size_t write_count { 0 };
halrtcnt_t read_duration_min { 0 };
halrtcnt_t read_duration_max { 0 };
halrtcnt_t read_test_duration { 0 };
size_t read_bytes { 0 };
size_t read_count { 0 };
};
SDCardTestThread(
) {
thread = chThdCreateFromHeap(NULL, 2048, NORMALPRIO + 10, SDCardTestThread::static_fn, this);
}
Result result() const {
return _result;
}
const Stats& stats() const {
return _stats;
}
~SDCardTestThread() {
chThdTerminate(thread);
chThdWait(thread);
}
private:
static constexpr size_t write_size = 16384;
static constexpr size_t bytes_to_write = 16 * 1024 * 1024;
static constexpr size_t bytes_to_read = bytes_to_write;
static Thread* thread;
volatile Result _result { Result::Incomplete };
Stats _stats;
static msg_t static_fn(void* arg) {
auto obj = static_cast<SDCardTestThread*>(arg);
obj->_result = obj->run();
return 0;
}
Result run() {
const std::string filename { "_PPTEST_.DAT" };
const auto write_result = write(filename);
if( write_result != Result::OK ) {
return write_result;
}
if( _stats.write_bytes < bytes_to_write ) {
return Result::FailWriteIncomplete;
}
if( chThdShouldTerminate() ) {
return Result::FailAbort;
}
const auto read_result = read(filename);
if( read_result != Result::OK ) {
return read_result;
}
f_unlink(filename.c_str());
if( _stats.read_bytes < bytes_to_read ) {
return Result::FailReadIncomplete;
}
if( chThdShouldTerminate() ) {
return Result::FailAbort;
}
return Result::OK;
}
Result write(const std::string& filename) {
const auto buffer = std::make_unique<std::array<uint8_t, write_size>>();
if( !buffer ) {
return Result::FailHeap;
}
File file { filename, File::openmode::out | File::openmode::binary | File::openmode::trunc };
if( !file.is_open() ) {
return Result::FailFileOpenWrite;
}
lfsr_word_t v = 1;
const halrtcnt_t test_start = halGetCounterValue();
while( !chThdShouldTerminate() && file.is_open() && (_stats.write_bytes < bytes_to_write) ) {
lfsr_fill(v,
reinterpret_cast<lfsr_word_t*>(buffer->data()),
sizeof(*buffer.get()) / sizeof(lfsr_word_t)
);
const halrtcnt_t write_start = halGetCounterValue();
if( !file.write(buffer->data(), buffer->size()) ) {
break;
}
const halrtcnt_t write_end = halGetCounterValue();
_stats.write_bytes += buffer->size();
_stats.write_count++;
const halrtcnt_t write_duration = write_end - write_start;
if( (_stats.write_duration_min == 0) || (write_duration < _stats.write_duration_min) ) {
_stats.write_duration_min = write_duration;
}
if( write_duration > _stats.write_duration_max ) {
_stats.write_duration_max = write_duration;
}
}
file.sync();
const halrtcnt_t test_end = halGetCounterValue();
_stats.write_test_duration = test_end - test_start;
return Result::OK;
}
Result read(const std::string& filename) {
const auto buffer = std::make_unique<std::array<uint8_t, write_size>>();
if( !buffer ) {
return Result::FailHeap;
}
File file { filename, File::openmode::in | File::openmode::binary };
if( !file.is_open() ) {
return Result::FailFileOpenRead;
}
lfsr_word_t v = 1;
const halrtcnt_t test_start = halGetCounterValue();
while( !chThdShouldTerminate() && file.is_open() && (_stats.read_bytes < bytes_to_read) ) {
const halrtcnt_t read_start = halGetCounterValue();
if( !file.read(buffer->data(), buffer->size()) ) {
break;
}
const halrtcnt_t read_end = halGetCounterValue();
_stats.read_bytes += buffer->size();
_stats.read_count++;
const halrtcnt_t read_duration = read_end - read_start;
if( (_stats.read_duration_min == 0) || (read_duration < _stats.read_duration_min) ) {
_stats.read_duration_min = read_duration;
}
if( read_duration > _stats.read_duration_max ) {
_stats.read_duration_max = read_duration;
}
if( !lfsr_compare(v,
reinterpret_cast<lfsr_word_t*>(buffer->data()),
sizeof(*buffer.get()) / sizeof(lfsr_word_t))
) {
return Result::FailCompare;
}
}
file.sync();
const halrtcnt_t test_end = halGetCounterValue();
_stats.read_test_duration = test_end - test_start;
return Result::OK;
}
};
Thread* SDCardTestThread::thread { nullptr };
namespace ui {
SDCardDebugView::SDCardDebugView(NavigationView& nav) {
add_children({ {
&text_title,
&text_detected_title,
&text_detected_value,
&text_bus_width_title,
&text_bus_width_value,
&text_card_mode_title,
&text_card_mode_value,
// &text_csd_title,
// &text_csd_value,
&text_block_size_title,
&text_block_size_value,
&text_block_count_title,
&text_block_count_value,
&text_capacity_title,
&text_capacity_value,
&text_test_write_time_title,
&text_test_write_time_value,
&text_test_write_rate_title,
&text_test_write_rate_value,
&text_test_read_time_title,
&text_test_read_time_value,
&text_test_read_rate_title,
&text_test_read_rate_value,
&button_test,
&button_ok,
} });
button_test.on_select = [this](Button&){ this->on_test(); };
button_ok.on_select = [&nav](Button&){ nav.pop(); };
}
void SDCardDebugView::on_show() {
sd_card_status_signal_token = sd_card::status_signal += [this](const sd_card::Status status) {
this->on_status(status);
};
on_status(sd_card::status());
}
void SDCardDebugView::on_hide() {
sd_card::status_signal -= sd_card_status_signal_token;
}
void SDCardDebugView::focus() {
button_ok.focus();
}
static std::string format_3dot3_string(const uint32_t value_in_thousandths) {
if( value_in_thousandths < 1000000U ) {
const uint32_t thousandths_part = value_in_thousandths % 1000;
const uint32_t integer_part = value_in_thousandths / 1000U;
return to_string_dec_uint(integer_part, 3) + "." + to_string_dec_uint(thousandths_part, 3, '0');
} else {
return "HHH.HHH";
}
}
static std::string format_bytes_size_string(uint64_t value) {
static const std::array<char, 5> suffix { { ' ', 'K', 'M', 'G', 'T' } };
size_t suffix_index = 1;
while( (value >= 1000000U) && (suffix_index < suffix.size()) ) {
value /= 1000U;
suffix_index++;
}
return format_3dot3_string(value) + " " + suffix[suffix_index] + "B";
}
void SDCardDebugView::on_status(const sd_card::Status) {
text_bus_width_value.set("");
text_card_mode_value.set("");
// text_csd_value.set("");
text_block_size_value.set("");
text_block_count_value.set("");
text_capacity_value.set("");
text_test_write_time_value.set("");
text_test_write_rate_value.set("");
text_test_read_time_value.set("");
text_test_read_rate_value.set("");
const bool is_inserted = sdcIsCardInserted(&SDCD1);
text_detected_value.set(is_inserted ? "Yes" : " No");
if( is_inserted ) {
const auto card_width_flags = LPC_SDMMC->CTYPE & 0x10001;
size_t card_width = 0;
switch(card_width_flags) {
case 0x00000: card_width = 1; break;
case 0x00001: card_width = 4; break;
case 0x10001: card_width = 8; break;
default: break;
}
text_bus_width_value.set(card_width ? to_string_dec_uint(card_width, 1) : "X");
text_card_mode_value.set("0x" + to_string_hex(SDCD1.cardmode, 8));
// text_csd_value.set("0x" + to_string_hex(SDCD1.csd, 8));
BlockDeviceInfo block_device_info;
if( sdcGetInfo(&SDCD1, &block_device_info) == CH_SUCCESS ) {
text_block_size_value.set(to_string_dec_uint(block_device_info.blk_size, 5));
text_block_count_value.set(to_string_dec_uint(block_device_info.blk_num, 9));
const uint64_t capacity = block_device_info.blk_size * uint64_t(block_device_info.blk_num);
text_capacity_value.set(format_bytes_size_string(capacity));
}
}
}
static std::string format_ticks_as_ms(const halrtcnt_t value) {
const uint32_t us = uint64_t(value) * 1000000U / halGetCounterFrequency();
return format_3dot3_string(us);
}
static std::string format_bytes_per_ticks_as_mib(const size_t bytes, const halrtcnt_t ticks) {
const uint32_t bps = uint64_t(bytes) * halGetCounterFrequency() / ticks;
const uint32_t kbps = bps / 1000U;
return format_3dot3_string(kbps);
}
void SDCardDebugView::on_test() {
text_test_write_time_value.set("");
text_test_write_rate_value.set("");
text_test_read_time_value.set("");
text_test_read_rate_value.set("");
SDCardTestThread thread;
// uint32_t spinner_phase = 0;
while( thread.result() == SDCardTestThread::Result::Incomplete ) {
chThdSleepMilliseconds(100);
// spinner_phase += 1;
// char c = '*';
// switch(spinner_phase % 4) {
// case 0: c = '-'; break;
// case 1: c = '\\'; break;
// case 2: c = '|'; break;
// case 3: c = '/'; break;
// default: c = '*'; break;
// }
// text_test_write_value.set({ c });
}
if( thread.result() == SDCardTestThread::Result::OK ) {
const auto stats = thread.stats();
const auto write_duration_avg = stats.write_test_duration / stats.write_count;
text_test_write_time_value.set(
format_ticks_as_ms(stats.write_duration_min) + "/" +
format_ticks_as_ms(write_duration_avg) + "/" +
format_ticks_as_ms(stats.write_duration_max)
);
text_test_write_rate_value.set(
format_bytes_per_ticks_as_mib(stats.write_bytes, stats.write_duration_min * stats.write_count) + " " +
format_bytes_per_ticks_as_mib(stats.write_bytes, stats.write_test_duration)
);
const auto read_duration_avg = stats.read_test_duration / stats.read_count;
text_test_read_time_value.set(
format_ticks_as_ms(stats.read_duration_min) + "/" +
format_ticks_as_ms(read_duration_avg) + "/" +
format_ticks_as_ms(stats.read_duration_max)
);
text_test_read_rate_value.set(
format_bytes_per_ticks_as_mib(stats.read_bytes, stats.read_duration_min * stats.read_count) + " " +
format_bytes_per_ticks_as_mib(stats.read_bytes, stats.read_test_duration)
);
} else {
text_test_write_time_value.set("Fail: " + to_string_dec_int(toUType(thread.result()), 4));
}
}
} /* namespace ui */