From 801f99edadbf37966f231a8ae4b06fd93ff58cb1 Mon Sep 17 00:00:00 2001
From: "Leandro A. F. Pereira" <leandro@hardinfo.org>
Date: Sat, 30 May 2020 21:59:29 -0700
Subject: [PATCH] SDCARD_READONLY (#17884)

---
 Marlin/Configuration_adv.h          |  2 ++
 Marlin/src/inc/SanityCheck.h        | 14 ++++++++++
 Marlin/src/sd/Sd2Card.cpp           | 21 +++++++++------
 Marlin/src/sd/SdBaseFile.cpp        | 40 ++++++++++++++++++++++++---
 Marlin/src/sd/SdVolume.cpp          | 26 +++++++++++-------
 Marlin/src/sd/cardreader.cpp        | 42 +++++++++++++++++------------
 buildroot/share/tests/LPC1768-tests |  3 +--
 7 files changed, 108 insertions(+), 40 deletions(-)

diff --git a/Marlin/Configuration_adv.h b/Marlin/Configuration_adv.h
index 22755f8122..ec6cb27ec4 100644
--- a/Marlin/Configuration_adv.h
+++ b/Marlin/Configuration_adv.h
@@ -1084,6 +1084,8 @@
   // Enable this option and set to HIGH if your SD cards are incorrectly detected.
   //#define SD_DETECT_STATE HIGH
 
+  //#define SDCARD_READONLY                 // Read-only SD card (to save over 2K of flash)
+
   #define SD_PROCEDURE_DEPTH 1              // Increase if you need more nested M32 calls
 
   #define SD_FINISHED_STEPPERRELEASE true   // Disable steppers when SD Print is finished
diff --git a/Marlin/src/inc/SanityCheck.h b/Marlin/src/inc/SanityCheck.h
index da29c0ecf0..ab3b6b8a11 100644
--- a/Marlin/src/inc/SanityCheck.h
+++ b/Marlin/src/inc/SanityCheck.h
@@ -2062,6 +2062,20 @@ static_assert(hbm[Z_AXIS] >= 0, "HOMING_BUMP_MM.Z must be greater than or equal
   #endif
 #endif
 
+/**
+ * Make sure features that need to write to the SD card are
+ * disabled unless write support is enabled.
+ */
+#if ENABLED(SDCARD_READONLY)
+  #if ENABLED(POWER_LOSS_RECOVERY)
+    #error "POWER_LOSS_RECOVERY is incompatible with SDCARD_READONLY."
+  #elif ENABLED(BINARY_FILE_TRANSFER)
+    #error "BINARY_FILE_TRANSFER is incompatible with SDCARD_READONLY."
+  #elif ENABLED(SDCARD_EEPROM_EMULATION)
+    #error "SDCARD_EEPROM_EMULATION is incompatible with SDCARD_READONLY."
+  #endif
+#endif
+
 /**
  * Make sure only one display is enabled
  */
diff --git a/Marlin/src/sd/Sd2Card.cpp b/Marlin/src/sd/Sd2Card.cpp
index e21662afc1..b5a3960890 100644
--- a/Marlin/src/sd/Sd2Card.cpp
+++ b/Marlin/src/sd/Sd2Card.cpp
@@ -179,8 +179,11 @@ void Sd2Card::chipSelect() {
  * \return true for success, false for failure.
  */
 bool Sd2Card::erase(uint32_t firstBlock, uint32_t lastBlock) {
+  if (ENABLED(SDCARD_READONLY)) return false;
+
   csd_t csd;
   if (!readCSD(&csd)) goto FAIL;
+
   // check for single block erase
   if (!csd.v1.erase_blk_en) {
     // erase size mask
@@ -535,9 +538,10 @@ bool Sd2Card::waitNotBusy(const millis_t timeout_ms) {
  * \return true for success, false for failure.
  */
 bool Sd2Card::writeBlock(uint32_t blockNumber, const uint8_t* src) {
-  if (type() != SD_CARD_TYPE_SDHC) blockNumber <<= 9;   // Use address if not SDHC card
+  if (ENABLED(SDCARD_READONLY)) return false;
 
   bool success = false;
+  if (type() != SD_CARD_TYPE_SDHC) blockNumber <<= 9;   // Use address if not SDHC card
   if (!cardCommand(CMD24, blockNumber)) {
     if (writeData(DATA_START_BLOCK, src)) {
       if (waitNotBusy(SD_WRITE_TIMEOUT)) {              // Wait for flashing to complete
@@ -561,6 +565,8 @@ bool Sd2Card::writeBlock(uint32_t blockNumber, const uint8_t* src) {
  * \return true for success, false for failure.
  */
 bool Sd2Card::writeData(const uint8_t* src) {
+  if (ENABLED(SDCARD_READONLY)) return false;
+
   bool success = true;
   chipSelect();
   // Wait for previous write to finish
@@ -574,14 +580,9 @@ bool Sd2Card::writeData(const uint8_t* src) {
 
 // Send one block of data for write block or write multiple blocks
 bool Sd2Card::writeData(const uint8_t token, const uint8_t* src) {
+  if (ENABLED(SDCARD_READONLY)) return false;
 
-  const uint16_t crc =
-    #if ENABLED(SD_CHECK_AND_RETRY)
-      CRC_CCITT(src, 512)
-    #else
-      0xFFFF
-    #endif
-  ;
+  const uint16_t crc = TERN(SD_CHECK_AND_RETRY, CRC_CCITT(src, 512), 0xFFFF);
   spiSendBlock(token, src);
   spiSend(crc >> 8);
   spiSend(crc & 0xFF);
@@ -607,6 +608,8 @@ bool Sd2Card::writeData(const uint8_t token, const uint8_t* src) {
  * \return true for success, false for failure.
  */
 bool Sd2Card::writeStart(uint32_t blockNumber, const uint32_t eraseCount) {
+  if (ENABLED(SDCARD_READONLY)) return false;
+
   bool success = false;
   if (!cardAcmd(ACMD23, eraseCount)) {                    // Send pre-erase count
     if (type() != SD_CARD_TYPE_SDHC) blockNumber <<= 9;   // Use address if not SDHC card
@@ -626,6 +629,8 @@ bool Sd2Card::writeStart(uint32_t blockNumber, const uint32_t eraseCount) {
  * \return true for success, false for failure.
  */
 bool Sd2Card::writeStop() {
+  if (ENABLED(SDCARD_READONLY)) return false;
+
   bool success = false;
   chipSelect();
   if (waitNotBusy(SD_WRITE_TIMEOUT)) {
diff --git a/Marlin/src/sd/SdBaseFile.cpp b/Marlin/src/sd/SdBaseFile.cpp
index 27c191a6a6..6e43c9f7c6 100644
--- a/Marlin/src/sd/SdBaseFile.cpp
+++ b/Marlin/src/sd/SdBaseFile.cpp
@@ -47,6 +47,8 @@ void (*SdBaseFile::dateTime_)(uint16_t* date, uint16_t* time) = 0;
 
 // add a cluster to a file
 bool SdBaseFile::addCluster() {
+  if (ENABLED(SDCARD_READONLY)) return false;
+
   if (!vol_->allocContiguous(1, &curCluster_)) return false;
 
   // if first cluster of file link to directory entry
@@ -60,6 +62,8 @@ bool SdBaseFile::addCluster() {
 // Add a cluster to a directory file and zero the cluster.
 // return with first block of cluster in the cache
 bool SdBaseFile::addDirCluster() {
+  if (ENABLED(SDCARD_READONLY)) return false;
+
   uint32_t block;
   // max folder size
   if (fileSize_ / sizeof(dir_t) >= 0xFFFF) return false;
@@ -153,6 +157,8 @@ bool SdBaseFile::contiguousRange(uint32_t* bgnBlock, uint32_t* endBlock) {
  *
  */
 bool SdBaseFile::createContiguous(SdBaseFile* dirFile, const char* path, uint32_t size) {
+  if (ENABLED(SDCARD_READONLY)) return false;
+
   uint32_t count;
   // don't allow zero length file
   if (size == 0) return false;
@@ -419,6 +425,8 @@ bool SdBaseFile::make83Name(const char* str, uint8_t* name, const char** ptr) {
  * directory, \a path is invalid or already exists in \a parent.
  */
 bool SdBaseFile::mkdir(SdBaseFile* parent, const char* path, bool pFlag) {
+  if (ENABLED(SDCARD_READONLY)) return false;
+
   uint8_t dname[11];
   SdBaseFile dir1, dir2;
   SdBaseFile* sub = &dir1;
@@ -449,6 +457,8 @@ bool SdBaseFile::mkdir(SdBaseFile* parent, const char* path, bool pFlag) {
 }
 
 bool SdBaseFile::mkdir(SdBaseFile* parent, const uint8_t dname[11]) {
+  if (ENABLED(SDCARD_READONLY)) return false;
+
   uint32_t block;
   dir_t d;
   dir_t* p;
@@ -632,7 +642,7 @@ bool SdBaseFile::open(SdBaseFile* dirFile, const uint8_t dname[11], uint8_t ofla
   }
   else {
     // don't create unless O_CREAT and O_WRITE
-    if (!(oflag & O_CREAT) || !(oflag & O_WRITE)) return false;
+    if ((oflag & (O_CREAT | O_WRITE)) != (O_CREAT | O_WRITE)) return false;
     if (emptyFound) {
       index = dirIndex_;
       p = cacheDirEntry(SdVolume::CACHE_FOR_WRITE);
@@ -716,8 +726,14 @@ bool SdBaseFile::open(SdBaseFile* dirFile, uint16_t index, uint8_t oflag) {
 
 // open a cached directory entry. Assumes vol_ is initialized
 bool SdBaseFile::openCachedEntry(uint8_t dirIndex, uint8_t oflag) {
+  dir_t* p;
+
+  #if ENABLED(SDCARD_READONLY)
+    if (oflag & (O_WRITE | O_CREAT | O_TRUNC)) goto FAIL;
+  #endif
+
   // location of entry in cache
-  dir_t* p = &vol_->cache()->dir[dirIndex];
+  p = &vol_->cache()->dir[dirIndex];
 
   // write or truncate is an error for a directory or read-only file
   if (p->attributes & (DIR_ATT_READ_ONLY | DIR_ATT_DIRECTORY)) {
@@ -1135,6 +1151,8 @@ dir_t* SdBaseFile::readDirCache() {
  * or an I/O error occurred.
  */
 bool SdBaseFile::remove() {
+  if (ENABLED(SDCARD_READONLY)) return false;
+
   dir_t* d;
   // free any clusters - will fail if read-only or directory
   if (!truncate(0)) return false;
@@ -1172,6 +1190,8 @@ bool SdBaseFile::remove() {
  * or an I/O error occurred.
  */
 bool SdBaseFile::remove(SdBaseFile* dirFile, const char* path) {
+  if (ENABLED(SDCARD_READONLY)) return false;
+
   SdBaseFile file;
   return file.open(dirFile, path, O_WRITE) ? file.remove() : false;
 }
@@ -1187,6 +1207,8 @@ bool SdBaseFile::remove(SdBaseFile* dirFile, const char* path) {
  * file, newPath is invalid or already exists, or an I/O error occurs.
  */
 bool SdBaseFile::rename(SdBaseFile* dirFile, const char* newPath) {
+  if (ENABLED(SDCARD_READONLY)) return false;
+
   dir_t entry;
   uint32_t dirCluster = 0;
   SdBaseFile file;
@@ -1279,6 +1301,8 @@ restore:
  * directory, is not empty, or an I/O error occurred.
  */
 bool SdBaseFile::rmdir() {
+  if (ENABLED(SDCARD_READONLY)) return false;
+
   // must be open subdirectory
   if (!isSubDir()) return false;
 
@@ -1317,6 +1341,8 @@ bool SdBaseFile::rmdir() {
  * \return true for success, false for failure.
  */
 bool SdBaseFile::rmRfStar() {
+  if (ENABLED(SDCARD_READONLY)) return false;
+
   uint32_t index;
   SdBaseFile f;
   rewind();
@@ -1424,7 +1450,7 @@ void SdBaseFile::setpos(filepos_t* pos) {
  */
 bool SdBaseFile::sync() {
   // only allow open files and directories
-  if (!isOpen()) goto FAIL;
+  if (ENABLED(SDCARD_READONLY) || !isOpen()) goto FAIL;
 
   if (flags_ & F_FILE_DIR_DIRTY) {
     dir_t* d = cacheDirEntry(SdVolume::CACHE_FOR_WRITE);
@@ -1524,6 +1550,8 @@ bool SdBaseFile::timestamp(SdBaseFile* file) {
  */
 bool SdBaseFile::timestamp(uint8_t flags, uint16_t year, uint8_t month,
                            uint8_t day, uint8_t hour, uint8_t minute, uint8_t second) {
+  if (ENABLED(SDCARD_READONLY)) return false;
+
   uint16_t dirDate, dirTime;
   dir_t* d;
 
@@ -1575,6 +1603,8 @@ bool SdBaseFile::timestamp(uint8_t flags, uint16_t year, uint8_t month,
  * \a length is greater than the current file size or an I/O error occurs.
  */
 bool SdBaseFile::truncate(uint32_t length) {
+  if (ENABLED(SDCARD_READONLY)) return false;
+
   uint32_t newPos;
   // error if not a normal file or read-only
   if (!isFile() || !(flags_ & O_WRITE)) return false;
@@ -1636,6 +1666,10 @@ bool SdBaseFile::truncate(uint32_t length) {
  *
  */
 int16_t SdBaseFile::write(const void* buf, uint16_t nbyte) {
+  #if ENABLED(SDCARD_READONLY)
+    writeError = true; return -1;
+  #endif
+
   // convert void* to uint8_t*  -  must be before goto statements
   const uint8_t* src = reinterpret_cast<const uint8_t*>(buf);
 
diff --git a/Marlin/src/sd/SdVolume.cpp b/Marlin/src/sd/SdVolume.cpp
index 1d4c56a344..0effc31aa5 100644
--- a/Marlin/src/sd/SdVolume.cpp
+++ b/Marlin/src/sd/SdVolume.cpp
@@ -46,6 +46,8 @@
 
 // find a contiguous group of clusters
 bool SdVolume::allocContiguous(uint32_t count, uint32_t* curCluster) {
+  if (ENABLED(SDCARD_READONLY)) return false;
+
   // start of group
   uint32_t bgnCluster;
   // end of group
@@ -117,18 +119,20 @@ bool SdVolume::allocContiguous(uint32_t count, uint32_t* curCluster) {
 }
 
 bool SdVolume::cacheFlush() {
-  if (cacheDirty_) {
-    if (!sdCard_->writeBlock(cacheBlockNumber_, cacheBuffer_.data))
-      return false;
-
-    // mirror FAT tables
-    if (cacheMirrorBlock_) {
-      if (!sdCard_->writeBlock(cacheMirrorBlock_, cacheBuffer_.data))
+  #if DISABLED(SDCARD_READONLY)
+    if (cacheDirty_) {
+      if (!sdCard_->writeBlock(cacheBlockNumber_, cacheBuffer_.data))
         return false;
-      cacheMirrorBlock_ = 0;
+
+      // mirror FAT tables
+      if (cacheMirrorBlock_) {
+        if (!sdCard_->writeBlock(cacheMirrorBlock_, cacheBuffer_.data))
+          return false;
+        cacheMirrorBlock_ = 0;
+      }
+      cacheDirty_ = 0;
     }
-    cacheDirty_ = 0;
-  }
+  #endif
   return true;
 }
 
@@ -190,6 +194,8 @@ bool SdVolume::fatGet(uint32_t cluster, uint32_t* value) {
 
 // Store a FAT entry
 bool SdVolume::fatPut(uint32_t cluster, uint32_t value) {
+  if (ENABLED(SDCARD_READONLY)) return false;
+
   uint32_t lba;
   // error if reserved cluster
   if (cluster < 2) return false;
diff --git a/Marlin/src/sd/cardreader.cpp b/Marlin/src/sd/cardreader.cpp
index 7be69beb91..9ce2c5304c 100644
--- a/Marlin/src/sd/cardreader.cpp
+++ b/Marlin/src/sd/cardreader.cpp
@@ -446,8 +446,8 @@ void CardReader::endFilePrint(TERN_(SD_RESORT, const bool re_sort/*=false*/)) {
 }
 
 void CardReader::openLogFile(char * const path) {
-  flag.logging = true;
-  openFileWrite(path);
+  flag.logging = DISABLED(SDCARD_READONLY);
+  TERN(SDCARD_READONLY,,openFileWrite(path));
 }
 
 //
@@ -573,15 +573,19 @@ void CardReader::openFileWrite(char * const path) {
   const char * const fname = diveToFile(false, curDir, path);
   if (!fname) return;
 
-  if (file.open(curDir, fname, O_CREAT | O_APPEND | O_WRITE | O_TRUNC)) {
-    flag.saving = true;
-    selectFileByName(fname);
-    TERN_(EMERGENCY_PARSER, emergency_parser.disable());
-    echo_write_to_file(fname);
-    ui.set_status(fname);
-  }
-  else
+  #if ENABLED(SDCARD_READONLY)
     openFailed(fname);
+  #else
+    if (file.open(curDir, fname, O_CREAT | O_APPEND | O_WRITE | O_TRUNC)) {
+      flag.saving = true;
+      selectFileByName(fname);
+      TERN_(EMERGENCY_PARSER, emergency_parser.disable());
+      echo_write_to_file(fname);
+      ui.set_status(fname);
+    }
+    else
+      openFailed(fname);
+  #endif
 }
 
 //
@@ -596,13 +600,17 @@ void CardReader::removeFile(const char * const name) {
   const char * const fname = diveToFile(false, curDir, name);
   if (!fname) return;
 
-  if (file.remove(curDir, fname)) {
-    SERIAL_ECHOLNPAIR("File deleted:", fname);
-    sdpos = 0;
-    TERN_(SDCARD_SORT_ALPHA, presort());
-  }
-  else
-    SERIAL_ECHOLNPAIR("Deletion failed, File: ", fname, ".");
+  #if ENABLED(SDCARD_READONLY)
+    SERIAL_ECHOLNPAIR("Deletion failed (read-only), File: ", fname, ".");
+  #else
+    if (file.remove(curDir, fname)) {
+      SERIAL_ECHOLNPAIR("File deleted:", fname);
+      sdpos = 0;
+      TERN_(SDCARD_SORT_ALPHA, presort());
+    }
+    else
+      SERIAL_ECHOLNPAIR("Deletion failed, File: ", fname, ".");
+  #endif
 }
 
 void CardReader::report_status() {
diff --git a/buildroot/share/tests/LPC1768-tests b/buildroot/share/tests/LPC1768-tests
index 332d22396e..2f206f02f2 100755
--- a/buildroot/share/tests/LPC1768-tests
+++ b/buildroot/share/tests/LPC1768-tests
@@ -15,8 +15,7 @@ set -e
 
 restore_configs
 opt_set MOTHERBOARD BOARD_RAMPS_14_RE_ARM_EFB
-opt_enable VIKI2 SDSUPPORT SERIAL_PORT_2 NEOPIXEL_LED
-
+opt_enable VIKI2 SDSUPPORT SDCARD_READONLY SERIAL_PORT_2 NEOPIXEL_LED
 opt_set NEOPIXEL_PIN P1_16
 exec_test $1 $2 "ReARM EFB VIKI2, SDSUPPORT, 2 Serial ports (USB CDC + UART0), NeoPixel"
 
-- 
GitLab