From 370f067fbf8b690cd0b22a27a1771d9171fb7d11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Martin?= Date: Mon, 5 Oct 2026 23:15:29 +0200 Subject: [PATCH] `sd card`: what the card says it is, from its CID register Type, size, and the identity register read by our SD driver (CMD10): manufacturer, OEM, product name, revision, serial and date, decoded by a host-tested parser. Needed for the upstream report of #21: nothing else here could read the card's identity. This one is a Samsung 8 GB SDHC from June 2013. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT --- README.md | 1 + lib/SD/src/sd_diskio.cpp | 20 +++++++++ lib/SD/src/sd_fault.h | 4 ++ lib/storage_model/src/sd_card_id.cpp | 53 +++++++++++++++++++++++ lib/storage_model/src/sd_card_id.h | 27 ++++++++++++ src/main.cpp | 15 ++++++- test/test_sd_card_id/test_sd_card_id.cpp | 54 ++++++++++++++++++++++++ 7 files changed, 173 insertions(+), 1 deletion(-) create mode 100644 lib/storage_model/src/sd_card_id.cpp create mode 100644 lib/storage_model/src/sd_card_id.h create mode 100644 test/test_sd_card_id/test_sd_card_id.cpp diff --git a/README.md b/README.md index e395a03..fc9ee3f 100644 --- a/README.md +++ b/README.md @@ -76,6 +76,7 @@ The LoRa Scanner (docs/milestones/M3.md) listens with the Cap's radio and **neve | `short` / `normal` | Screen timeouts 5 s / 10 s, or 30 s / 60 s | | `wifi add ` | Adds a Saved Network (so credentials stay out of the repo) | | `log ` | Appends a line to a test IRC Log (`/irc/dev/#test/.log`) | +| `sd card` | What the SD card says it is: type, size, and its identity register (maker, name, revision, serial, date) | | `sd list` | Lists the files of each Storage Clean-up category | | `cat ` | Prints the first ~1.2 KB of a file on the SD card | | `irc start` | Starts the IRC Service (normally done by opening the IRC App) | diff --git a/lib/SD/src/sd_diskio.cpp b/lib/SD/src/sd_diskio.cpp index 7d003a6..fd42d7f 100644 --- a/lib/SD/src/sd_diskio.cpp +++ b/lib/SD/src/sd_diskio.cpp @@ -920,6 +920,26 @@ uint32_t sdcard_sector_size(uint8_t pdrv) { return 512; } +// roro: the card's CID. CMD10 in SPI mode (the enum's SEND_CID, 2, is the SD-mode command). +namespace roro { +bool sdReadCid(uint8_t cid[16]) { + for (uint8_t pdrv = 0; pdrv < FF_VOLUMES; ++pdrv) { + ardu_sdcard_t *card = s_cards[pdrv]; + if (!card) { + continue; + } + AcquireSPI lock(card); + if (!sdSelectCard(pdrv)) { + return false; + } + bool ok = !sdCommand(pdrv, 10, 0, NULL) && sdReadBytes(pdrv, (char *)cid, 16); + sdDeselectCard(pdrv); + return ok; + } + return false; +} +} // namespace roro + sdcard_type_t sdcard_type(uint8_t pdrv) { ardu_sdcard_t *card = s_cards[pdrv]; if (pdrv >= FF_VOLUMES || card == NULL) { diff --git a/lib/SD/src/sd_fault.h b/lib/SD/src/sd_fault.h index 347f892..bf02ab4 100644 --- a/lib/SD/src/sd_fault.h +++ b/lib/SD/src/sd_fault.h @@ -26,4 +26,8 @@ struct SdFault { SdFault sdLastFault(); +// The mounted card's identity register (CID, CMD10): who made it, its name, serial and date. +// Call it where card access is allowed (the storage task). +bool sdReadCid(uint8_t cid[16]); + } // namespace roro diff --git a/lib/storage_model/src/sd_card_id.cpp b/lib/storage_model/src/sd_card_id.cpp new file mode 100644 index 0000000..21a2cdc --- /dev/null +++ b/lib/storage_model/src/sd_card_id.cpp @@ -0,0 +1,53 @@ +#include "sd_card_id.h" + +#include + +namespace roro { + +namespace { +char printable(uint8_t b) { return b >= 0x20 && b < 0x7F ? static_cast(b) : '?'; } +} // namespace + +SdCardId parseSdCid(const uint8_t cid[16]) { + SdCardId id; + id.manufacturer = cid[0]; + for (int i = 0; i < 2; ++i) id.oem[i] = printable(cid[1 + i]); + for (int i = 0; i < 5; ++i) id.product[i] = printable(cid[3 + i]); + id.revisionMajor = cid[8] >> 4; + id.revisionMinor = cid[8] & 0x0F; + id.serial = static_cast(cid[9]) << 24 | cid[10] << 16 | cid[11] << 8 | cid[12]; + // 4 reserved bits, 8 bits of year since 2000, 4 bits of month. + id.year = 2000 + (((cid[13] & 0x0F) << 4) | (cid[14] >> 4)); + id.month = cid[14] & 0x0F; + return id; +} + +const char* sdManufacturerName(uint8_t id) { + switch (id) { + case 0x01: return "Panasonic"; + case 0x02: return "Toshiba/Kioxia"; + case 0x03: return "SanDisk"; + case 0x1B: return "Samsung"; + case 0x1D: return "ADATA"; + case 0x27: return "Phison"; + case 0x28: return "Lexar"; + case 0x31: return "Silicon Power"; + case 0x41: return "Kingston"; + case 0x74: return "Transcend"; + case 0x76: return "Patriot"; + case 0x82: return "Sony"; + case 0x9C: return "Angelbird/Hoodman"; + case 0xAD: return "Longsys/Lexar"; + default: return "unknown"; + } +} + +std::string sdCardSummary(const SdCardId& id) { + char s[112]; + std::snprintf(s, sizeof s, "%s (0x%02X) \"%s\" \"%s\" rev %u.%u, serial %08x, made %04d-%02d", + sdManufacturerName(id.manufacturer), id.manufacturer, id.oem, id.product, id.revisionMajor, + id.revisionMinor, static_cast(id.serial), id.year, id.month); + return s; +} + +} // namespace roro diff --git a/lib/storage_model/src/sd_card_id.h b/lib/storage_model/src/sd_card_id.h new file mode 100644 index 0000000..30e3de3 --- /dev/null +++ b/lib/storage_model/src/sd_card_id.h @@ -0,0 +1,27 @@ +#pragma once + +#include +#include + +namespace roro { + +// What an SD card says it is: its CID register, 16 bytes (SD Physical Layer spec, 5.2). +struct SdCardId { + uint8_t manufacturer = 0; // assigned by the SD Association; see sdManufacturerName() + char oem[3] = {}; // two characters + char product[6] = {}; // five characters + uint8_t revisionMajor = 0, revisionMinor = 0; + uint32_t serial = 0; + int year = 0, month = 0; // manufactured +}; + +SdCardId parseSdCid(const uint8_t cid[16]); + +// The usual holder of a manufacturer ID. The SD Association doesn't publish its list: these are +// the commonly reported ones, and resold or counterfeit cards carry whatever their maker chose. +const char* sdManufacturerName(uint8_t id); + +// "SanDisk (0x03) "SD" "SU08G" rev 8.0, serial 1234abcd, made 2014-03". +std::string sdCardSummary(const SdCardId& id); + +} // namespace roro diff --git a/src/main.cpp b/src/main.cpp index c492b66..36beec4 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -23,6 +23,7 @@ #include "platform/crash_report.h" #include "platform/nvs_store.h" #include "platform/system_info.h" +#include "sd_card_id.h" #include "sd_fault.h" #include "service_manager.h" #include "platform/identity.h" @@ -376,7 +377,7 @@ static const char* const kHelp = "gemini get fetch a Gemini page and report header, size, certificate, heap\n" "irc start | irc stop | irc dump | irc say \n" "ls [folder] | rm | install (Update from SD)\n" - "sd list | cat | log | burst | sound on|off | short | normal\n" + "sd card | sd list | cat | log | burst | sound on|off | short | normal\n" #ifdef RORO_DEBUG "crash abort|wdt crash on purpose (to test crash reports and Safe Mode)\n" "lora inject [rssi] [snr] a packet into the LoRa Scanner as if received (nothing is sent)\n" @@ -538,6 +539,18 @@ static void runCommand(String line) { } } if (line.startsWith("sd put ")) startUpload(line.substring(7)); // then raw bytes: see uploadStep() + if (line == "sd card") { // what the card says it is, from its CID register + if (!storageService->state().present) return (void)console.println("sd card: no card"); + storageService->runJob([]() { + uint8_t cid[16]; + const char* type = SD.cardType() == CARD_SDHC ? "SDHC/SDXC" : SD.cardType() == CARD_SD ? "SDSC" : "MMC or unknown"; + if (!sdReadCid(cid)) return (void)console.println("sd card: the card didn't answer"); + console.printf("sd card: %s, %.1f GB, %s\n", type, SD.cardSize() / 1e9, sdCardSummary(parseSdCid(cid)).c_str()); + console.printf("sd card: CID"); + for (uint8_t b : cid) console.printf(" %02x", b); + console.println(); + }); + } if (line == "sd list") { storageService->requestListing(); listingWanted = true; diff --git a/test/test_sd_card_id/test_sd_card_id.cpp b/test/test_sd_card_id/test_sd_card_id.cpp new file mode 100644 index 0000000..a946c5d --- /dev/null +++ b/test/test_sd_card_id/test_sd_card_id.cpp @@ -0,0 +1,54 @@ +#include + +#include "sd_card_id.h" + +using namespace roro; + +void setUp() {} +void tearDown() {} + +// A SanDisk 8 GB card's CID: manufacturer 0x03, OEM "SD", product "SU08G", revision 8.0, +// serial 0x1234ABCD, made in March 2014. +static const uint8_t kSanDisk[16] = {0x03, 'S', 'D', 'S', 'U', '0', '8', 'G', 0x80, + 0x12, 0x34, 0xAB, 0xCD, 0x00, 0xE3, 0xFF}; + +void test_fields() { + SdCardId id = parseSdCid(kSanDisk); + TEST_ASSERT_EQUAL_HEX8(0x03, id.manufacturer); + TEST_ASSERT_EQUAL_STRING("SD", id.oem); + TEST_ASSERT_EQUAL_STRING("SU08G", id.product); + TEST_ASSERT_EQUAL_UINT8(8, id.revisionMajor); + TEST_ASSERT_EQUAL_UINT8(0, id.revisionMinor); + TEST_ASSERT_EQUAL_HEX32(0x1234ABCD, id.serial); + TEST_ASSERT_EQUAL_INT(2014, id.year); + TEST_ASSERT_EQUAL_INT(3, id.month); +} + +// Clone cards put anything in the name: unprintable bytes mustn't reach the console. +void test_unprintable_names() { + uint8_t cid[16] = {0x00, 0x00, 0x01, 'A', 0x00, 0xFF, 'B', ' ', 0x10}; + SdCardId id = parseSdCid(cid); + TEST_ASSERT_EQUAL_STRING("??", id.oem); + TEST_ASSERT_EQUAL_STRING("A??B ", id.product); + TEST_ASSERT_EQUAL_UINT8(1, id.revisionMajor); +} + +void test_manufacturer_names() { + TEST_ASSERT_EQUAL_STRING("SanDisk", sdManufacturerName(0x03)); + TEST_ASSERT_EQUAL_STRING("Samsung", sdManufacturerName(0x1B)); + TEST_ASSERT_EQUAL_STRING("unknown", sdManufacturerName(0xEE)); +} + +void test_summary_line() { + TEST_ASSERT_EQUAL_STRING("SanDisk (0x03) \"SD\" \"SU08G\" rev 8.0, serial 1234abcd, made 2014-03", + sdCardSummary(parseSdCid(kSanDisk)).c_str()); +} + +int main() { + UNITY_BEGIN(); + RUN_TEST(test_fields); + RUN_TEST(test_unprintable_names); + RUN_TEST(test_manufacturer_names); + RUN_TEST(test_summary_line); + return UNITY_END(); +}