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 |
|
| `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) |
|
| `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`) |
|
| `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 |
|
| `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 |
|
| `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) |
|
| `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;
|
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) {
|
sdcard_type_t sdcard_type(uint8_t pdrv) {
|
||||||
ardu_sdcard_t *card = s_cards[pdrv];
|
ardu_sdcard_t *card = s_cards[pdrv];
|
||||||
if (pdrv >= FF_VOLUMES || card == NULL) {
|
if (pdrv >= FF_VOLUMES || card == NULL) {
|
||||||
|
|||||||
@@ -26,4 +26,8 @@ struct SdFault {
|
|||||||
|
|
||||||
SdFault sdLastFault();
|
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
|
} // 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/crash_report.h"
|
||||||
#include "platform/nvs_store.h"
|
#include "platform/nvs_store.h"
|
||||||
#include "platform/system_info.h"
|
#include "platform/system_info.h"
|
||||||
|
#include "sd_card_id.h"
|
||||||
#include "sd_fault.h"
|
#include "sd_fault.h"
|
||||||
#include "service_manager.h"
|
#include "service_manager.h"
|
||||||
#include "platform/identity.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"
|
"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"
|
"irc start | irc stop | irc dump | irc say <buffer> <text>\n"
|
||||||
"ls [folder] | rm <path> | install <path.ota> (Update from SD)\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
|
#ifdef RORO_DEBUG
|
||||||
"crash abort|wdt crash on purpose (to test crash reports and Safe Mode)\n"
|
"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"
|
"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.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") {
|
if (line == "sd list") {
|
||||||
storageService->requestListing();
|
storageService->requestListing();
|
||||||
listingWanted = true;
|
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