Public Access
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 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EhqxQ49eCju4CzKYNjZzwT
This commit is contained in:
@@ -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 <ssid><TAB><password>` | Adds a Saved Network (so credentials stay out of the repo) |
|
||||
| `log <text>` | Appends a line to a test IRC Log (`/irc/dev/#test/<date>.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 <path>` | 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) |
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
#include "sd_card_id.h"
|
||||
|
||||
#include <cstdio>
|
||||
|
||||
namespace roro {
|
||||
|
||||
namespace {
|
||||
char printable(uint8_t b) { return b >= 0x20 && b < 0x7F ? static_cast<char>(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<uint32_t>(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<unsigned>(id.serial), id.year, id.month);
|
||||
return s;
|
||||
}
|
||||
|
||||
} // namespace roro
|
||||
@@ -0,0 +1,27 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
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
|
||||
+14
-1
@@ -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 <url> fetch a Gemini page and report header, size, certificate, heap\n"
|
||||
"irc start | irc stop | irc dump | irc say <buffer> <text>\n"
|
||||
"ls [folder] | rm <path> | install <path.ota> (Update from SD)\n"
|
||||
"sd list | cat <path> | log <text> | burst | sound on|off | short | normal\n"
|
||||
"sd card | sd list | cat <path> | log <text> | 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 <hex> [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;
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
#include <unity.h>
|
||||
|
||||
#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();
|
||||
}
|
||||
Reference in New Issue
Block a user