/* * 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 "diskio.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, }; std::string ResultStr[10] = { "Compare", "Read incomplete", "Write incomplete", "Abort", "File Open Read", "File Open Write", "Heap", "Thread", "Incomplete", "OK", }; struct Stats { halrtcnt_t write_duration_min{0}; halrtcnt_t write_duration_max{0}; halrtcnt_t write_test_duration{0}; File::Size 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}; File::Size read_bytes{0}; size_t read_count{0}; }; SDCardTestThread() { thread = chThdCreateFromHeap(NULL, 3072, 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 File::Size write_size = 16384; static constexpr File::Size bytes_to_write = 16 * 1024 * 1024; static constexpr File::Size 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(arg); obj->_result = obj->run(); return 0; } Result run() { const std::filesystem::path filename{u"_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(reinterpret_cast(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::filesystem::path& filename) { const auto buffer = std::make_unique>(); if (!buffer) { return Result::FailHeap; } File file; auto file_create_error = file.create(filename); if (file_create_error.is_valid()) { return Result::FailFileOpenWrite; } lfsr_word_t v = 1; const halrtcnt_t test_start = halGetCounterValue(); while (!chThdShouldTerminate() && (_stats.write_bytes < bytes_to_write)) { lfsr_fill(v, reinterpret_cast(buffer->data()), sizeof(*buffer.get()) / sizeof(lfsr_word_t)); const halrtcnt_t write_start = halGetCounterValue(); const auto result_write = file.write(buffer->data(), buffer->size()); if (result_write.is_error()) { 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::filesystem::path& filename) { const auto buffer = std::make_unique>(); if (!buffer) { return Result::FailHeap; } File file; auto file_open_error = file.open(filename); if (file_open_error.is_valid()) { return Result::FailFileOpenRead; } lfsr_word_t v = 1; const halrtcnt_t test_start = halGetCounterValue(); while (!chThdShouldTerminate() && (_stats.read_bytes < bytes_to_read)) { const halrtcnt_t read_start = halGetCounterValue(); const auto result_read = file.read(buffer->data(), buffer->size()); if (result_read.is_error()) { 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(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({ &labels, &text_format, &text_csd_value_3, &text_csd_value_2, &text_csd_value_1, &text_csd_value_0, &text_bus_width_value, &text_card_type_value, &text_block_size_value, &text_block_count_value, &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 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_type_value.set(""); text_csd_value_0.set(""); text_csd_value_1.set(""); text_csd_value_2.set(""); text_csd_value_3.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); 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"); // TODO: Implement Text class right-justify! BYTE card_type; disk_ioctl(0, MMC_GET_TYPE, &card_type); std::string formatted_card_type; switch (card_type & SDC_MODE_CARDTYPE_MASK) { case SDC_MODE_CARDTYPE_SDV11: formatted_card_type = "SD V1.1"; break; case SDC_MODE_CARDTYPE_SDV20: formatted_card_type = "SD V2.0"; break; case SDC_MODE_CARDTYPE_MMC: formatted_card_type = "MMC"; break; default: formatted_card_type = "???"; break; } if (card_type & SDC_MODE_HIGH_CAPACITY) { formatted_card_type += ", SDHC"; } text_card_type_value.set(formatted_card_type); std::array csd; disk_ioctl(0, MMC_GET_CSD, csd.data()); text_csd_value_3.set(to_string_hex(csd[3], 8)); text_csd_value_2.set(to_string_hex(csd[2], 8)); text_csd_value_1.set(to_string_hex(csd[1], 8)); text_csd_value_0.set(to_string_hex(csd[0], 8)); text_format.set(fetch_sdcard_format()); if (fetch_sdcard_format().find("FAT32") != std::string::npos) { // to satisfy the intendent style in this app, the text contains padding space, thus can't use == text_format.set_style(Theme::getInstance()->fg_green); } else { text_format.set_style(Theme::getInstance()->error_dark); } 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 File::Size 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: " + thread.ResultStr[thread.result() + 8]); } } std::string SDCardDebugView::fetch_sdcard_format() { const size_t max_len = sizeof("Undefined: 255") + 1; auto padding = [max_len](std::string s) { if (s.length() < max_len) { return std::string(max_len - s.length(), ' ') + s; } return s; }; FATFS* fs = &sd_card::fs; std::string resault; switch (fs->fs_type) { case FS_FAT12: resault = "FAT12"; break; case FS_FAT16: resault = "FAT16"; break; case FS_FAT32: resault = "FAT32"; break; case FS_EXFAT: // TODO: a bug from filesystem can't detect exfat, issue not from here resault = "ExFAT"; break; default: resault = "Undefined: " + to_string_dec_uint(fs->fs_type, 1); break; } return padding(resault); } } /* namespace ui */