From 12b260e200ef1a0e44545d6c26ba8b2473b48bab Mon Sep 17 00:00:00 2001
From: holub <andrei.holub@gmail.com>
Date: Wed, 1 Dec 2021 18:49:48 -0500
Subject: [PATCH] spi_sdcard: add CMD18 - CMD_READ_MULTIPLE_BLOCK (#8913)

* spi_sdcard: add CMD18 - CMD_READ_MULTIPLE_BLOCK and clean up state changes
---
 .gitignore                         |   1 +
 src/devices/machine/spi_sdcard.cpp | 436 +++++++++++++++++------------
 src/devices/machine/spi_sdcard.h   |  29 +-
 3 files changed, 280 insertions(+), 186 deletions(-)

diff --git a/.gitignore b/.gitignore
index 4cd57d9f2bf..f5231d17f98 100644
--- a/.gitignore
+++ b/.gitignore
@@ -42,6 +42,7 @@
 !/*.bdf
 !/LICENSE
 /.idea
+.vscode
 docs/build
 docs/source/_build
 regtests/chdman/temp
diff --git a/src/devices/machine/spi_sdcard.cpp b/src/devices/machine/spi_sdcard.cpp
index 3ace0591742..588727d83d4 100644
--- a/src/devices/machine/spi_sdcard.cpp
+++ b/src/devices/machine/spi_sdcard.cpp
@@ -18,6 +18,8 @@
 
     Refrences:
     https://www.sdcard.org/downloads/pls/ (Physical Layer Simplified Specification)
+    REF: tags are refering to the spec form above. 'Physical Layer Simplified Specification v8.00'
+
     http://www.dejazzer.com/ee379/lecture_notes/lec12_sd_card.pdf
     https://embdev.net/attachment/39390/TOSHIBA_SD_Card_Specification.pdf
     http://elm-chan.org/docs/mmc/mmc_e.html
@@ -46,9 +48,12 @@ spi_sdcard_device::spi_sdcard_device(const machine_config &mconfig, device_type
 	device_t(mconfig, type, tag, owner, clock),
 	write_miso(*this),
 	m_image(*this, "image"),
+	m_state(SD_STATE_IDLE),
 	m_harddisk(nullptr),
-	m_in_latch(0), m_out_latch(0), m_cmd_ptr(0), m_state(0), m_out_ptr(0), m_out_count(0), m_ss(0), m_in_bit(0),
-	m_cur_bit(0), m_write_ptr(0), m_blksize(512), m_bACMD(false)
+	m_ss(0), m_in_bit(0),
+	m_in_latch(0), m_out_latch(0xff), m_cur_bit(0),
+	m_out_count(0), m_out_ptr(0), m_write_ptr(0), m_blksize(512), m_blknext(0),
+	m_bACMD(false)
 {
 }
 
@@ -64,13 +69,15 @@ spi_sdcard_sdhc_device::spi_sdcard_sdhc_device(const machine_config &mconfig, co
 	m_type = SD_TYPE_HC;
 }
 
+ALLOW_SAVE_TYPE(spi_sdcard_device::sd_state);
+ALLOW_SAVE_TYPE(spi_sdcard_device::sd_type);
+
 void spi_sdcard_device::device_start()
 {
 	write_miso.resolve_safe();
+	save_item(NAME(m_state));
 	save_item(NAME(m_in_latch));
 	save_item(NAME(m_out_latch));
-	save_item(NAME(m_cmd_ptr));
-	save_item(NAME(m_state));
 	save_item(NAME(m_out_ptr));
 	save_item(NAME(m_out_count));
 	save_item(NAME(m_ss));
@@ -78,6 +85,7 @@ void spi_sdcard_device::device_start()
 	save_item(NAME(m_cur_bit));
 	save_item(NAME(m_write_ptr));
 	save_item(NAME(m_blksize));
+	save_item(NAME(m_blknext));
 	save_item(NAME(m_type));
 	save_item(NAME(m_cmd));
 	save_item(NAME(m_data));
@@ -94,10 +102,11 @@ void spi_sdcard_device::device_add_mconfig(machine_config &config)
 	HARDDISK(config, m_image).set_interface("spi_sdcard");
 }
 
-void spi_sdcard_device::send_data(int count)
+void spi_sdcard_device::send_data(u16 count, sd_state new_state)
 {
 	m_out_ptr = 0;
 	m_out_count = count;
+	change_state(new_state);
 }
 
 void spi_sdcard_device::spi_clock_w(int state)
@@ -121,56 +130,73 @@ void spi_sdcard_device::spi_clock_w(int state)
 			if (m_cur_bit == 8)
 			{
 				LOGMASKED(LOG_SPI, "SDCARD: got %02x\n", m_in_latch);
+				for (u8 i = 0; i < 5; i++)
+				{
+					m_cmd[i] = m_cmd[i + 1];
+				}
+				m_cmd[5] = m_in_latch;
 
 				switch (m_state)
 				{
-					case SD_STATE_IDLE:
-						for (int i = 0; i < 5; i++)
+				case SD_STATE_IDLE:
+					do_command();
+					break;
+
+				case SD_STATE_WRITE_WAITFE:
+					if (m_in_latch == 0xfe)
+					{
+						m_state = SD_STATE_WRITE_DATA;
+						m_out_latch = 0xff;
+						m_write_ptr = 0;
+						change_state(SD_STATE_RCV);
+					}
+					break;
+
+				case SD_STATE_WRITE_DATA:
+					m_data[m_write_ptr++] = m_in_latch;
+					if (m_write_ptr == (m_blksize + 2))
+					{
+						u32 blk = (u32(m_cmd[1]) << 24) | (u32(m_cmd[2]) << 16) | (u32(m_cmd[3]) << 8) | u32(m_cmd[4]);
+						if (m_type == SD_TYPE_V2)
 						{
-							m_cmd[i] = m_cmd[i + 1];
+							blk /= m_blksize;
 						}
-						m_cmd[5] = m_in_latch;
 
-						if ((((m_cmd[0] & 0xc0) == 0x40) && (m_cmd[5] & 1)) && (m_out_count == 0))
+						LOGMASKED(LOG_GENERAL, "writing LBA %x, data %02x %02x %02x %02x\n", blk, m_data[0], m_data[1], m_data[2], m_data[3]);
+						if (hard_disk_write(m_harddisk, blk, &m_data[0]))
 						{
-							do_command();
+							m_data[0] = DATA_RESPONSE_OK;
 						}
-						break;
-
-					case SD_STATE_WRITE_WAITFE:
-						if (m_in_latch == 0xfe)
+						else
 						{
-							m_state = SD_STATE_WRITE_DATA;
-							m_out_latch = 0xff;
-							m_write_ptr = 0;
+							m_data[0] = DATA_RESPONSE_IO_ERROR;
 						}
-						break;
+						m_data[1] = 0x01;
 
-					case SD_STATE_WRITE_DATA:
-						m_data[m_write_ptr++] = m_in_latch;
-						if (m_write_ptr == (m_blksize + 2))
-						{
-							u32 blk = (m_cmd[1] << 24) | (m_cmd[2] << 16) | (m_cmd[3] << 8) | m_cmd[4];
-							if (m_type == SD_TYPE_V2)
-							{
-								blk /= m_blksize;
-							}
+						send_data(2, SD_STATE_IDLE);
+					}
+					break;
 
-							LOGMASKED(LOG_GENERAL, "writing LBA %x, data %02x %02x %02x %02x\n", blk, m_data[0], m_data[1], m_data[2], m_data[3]);
-							if (hard_disk_write(m_harddisk, blk, &m_data[0]))
-							{
-								m_data[0] = DATA_RESPONSE_OK;
-							}
-							else
-							{
-								m_data[0] = DATA_RESPONSE_IO_ERROR;
-							}
-							m_data[1] = 0x01;
+				case SD_STATE_DATA_MULTI:
+					do_command();
+					if (m_state == SD_STATE_DATA_MULTI && m_out_count == 0)
+					{
+						m_data[0] = 0xfe; // data token
+						hard_disk_read(m_harddisk, m_blknext++, &m_data[1]);
+						util::crc16_t crc16 = util::crc16_creator::simple(
+							&m_data[1], m_blksize);
+						m_data[m_blksize + 1] = (crc16 >> 8) & 0xff;
+						m_data[m_blksize + 2] = (crc16 & 0xff);
+						send_data(1 + m_blksize + 2, SD_STATE_DATA_MULTI);
+					}
+					break;
 
-							m_state = SD_STATE_IDLE;
-							send_data(2);
-						}
-						break;
+				default:
+					if (((m_cmd[0] & 0x70) == 0x40) || (m_out_count == 0)) // CMD0 - GO_IDLE_STATE
+					{
+						do_command();
+					}
+					break;
 				}
 			}
 		}
@@ -178,12 +204,11 @@ void spi_sdcard_device::spi_clock_w(int state)
 		{
 			m_in_latch <<= 1;
 			m_out_latch <<= 1;
-			LOGMASKED(LOG_SPI, "\tsdcard: S %02x %02x (%d)\n", m_in_latch, m_out_latch, m_cur_bit);
-			if (m_cur_bit == 8)
-			{
-				m_cur_bit = 0;
-			}
+			m_out_latch |= 1;
+			LOGMASKED(LOG_SPI, "\tsdcard: S %02x %02x (%d)\n", m_in_latch,
+					  m_out_latch, m_cur_bit);
 
+			m_cur_bit &= 0x07;
 			if (m_cur_bit == 0)
 			{
 				if (m_out_count > 0)
@@ -193,163 +218,214 @@ void spi_sdcard_device::spi_clock_w(int state)
 					m_out_count--;
 				}
 			}
-
-			write_miso(BIT(m_out_latch, 7)  ? ASSERT_LINE : CLEAR_LINE);
+			write_miso(BIT(m_out_latch, 7));
 		}
 	}
 }
 
 void spi_sdcard_device::do_command()
 {
-	LOGMASKED(LOG_COMMAND, "SDCARD: cmd %02d %02x %02x %02x %02x %02x\n", m_cmd[0] & 0x3f, m_cmd[1], m_cmd[2], m_cmd[3], m_cmd[4], m_cmd[5]);
-	switch (m_cmd[0] & 0x3f)
+	if (((m_cmd[0] & 0xc0) == 0x40) && (m_cmd[5] & 1))
 	{
-	case 0: // CMD0 - GO_IDLE_STATE
-		if (m_harddisk)
+		LOGMASKED(LOG_COMMAND, "SDCARD: cmd %02d %02x %02x %02x %02x %02x\n", m_cmd[0] & 0x3f, m_cmd[1], m_cmd[2], m_cmd[3], m_cmd[4], m_cmd[5]);
+		bool clean_cmd = true;
+		switch (m_cmd[0] & 0x3f)
 		{
-			m_data[0] = 0x01;
-		}
-		else
-		{
-			m_data[0] = 0x00;
-		}
-		send_data(1);
-		break;
+		case 0: // CMD0 - GO_IDLE_STATE
+			if (m_harddisk)
+			{
+				m_data[0] = 0x01;
+				send_data(1, SD_STATE_IDLE);
+			}
+			else
+			{
+				m_data[0] = 0x00;
+				send_data(1, SD_STATE_INA);
+			}
+			break;
 
-	case 8: // CMD8 - SEND_IF_COND (SD v2 only)
-		m_data[0] = 0x01;
-		m_data[1] = 0;
-		m_data[2] = 0;
-		m_data[3] = 0;
-		m_data[4] = 0xaa;
-		send_data(5);
-		break;
+		case 8:					  // CMD8 - SEND_IF_COND (SD v2 only)
+			m_data[0] = 0;		  // 0x01;
+			m_data[1] = m_cmd[1]; // 0;
+			m_data[2] = m_cmd[2]; // 0;
+			m_data[3] = m_cmd[3]; // 0x01;
+			m_data[4] = m_cmd[4]; // 0xaa;
+			send_data(5, SD_STATE_IDLE);
+			break;
 
-	case 10: // CMD10 - SEND_CID
-		m_data[0] = 0x01;   // initial R1 response
-		m_data[1] = 0x00;   // throwaway byte before data transfer
-		m_data[2] = 0xfe;   // data token
-		m_data[3] =  'M';   // Manufacturer ID - we'll use M for MAME
-		m_data[4] =  'M';   // OEM ID - MD for MAMEdev
-		m_data[5] =  'D';
-		m_data[6] =  'M';   // Product Name - "MCARD"
-		m_data[7] =  'C';
-		m_data[8] =  'A';
-		m_data[9] =  'R';
-		m_data[10] = 'D';
-		m_data[11] = 0x10;  // Product Revision in BCD (1.0)
-		{
-			u32 uSerial = 0x12345678;
-			m_data[12] = (uSerial>>24) & 0xff;  // PSN - Product Serial Number
-			m_data[13] = (uSerial>>16) & 0xff;
-			m_data[14] = (uSerial>>8) & 0xff;
-			m_data[15] = (uSerial & 0xff);
-		}
-		m_data[16] = 0x01;  // MDT - Manufacturing Date
-		m_data[17] = 0x59;  // 0x15 9 = 2021, September
-		m_data[18] = 0x00;  // CRC7, bit 0 is always 0
-		{
-			util::crc16_t crc16 = util::crc16_creator::simple(&m_data[3], 16);
-			m_data[19] = (crc16 >> 8) & 0xff;
-			m_data[20] = (crc16 & 0xff);
-		}
-		send_data(3 + 16 + 2);
-		break;
-
-	case 16: // CMD16 - SET_BLOCKLEN
-		m_blksize = (m_cmd[3] << 8) | m_cmd[4];
-		if (hard_disk_set_block_size(m_harddisk, m_blksize))
-		{
-			m_data[0] = 0;
-		}
-		else
-		{
-			m_data[0] = 0xff;   // indicate an error
-			// if false was returned, it means the hard disk is a CHD file, and we can't resize the
-			// blocks on CHD files.
-			logerror("spi_sdcard: Couldn't change block size to %d, wrong CHD file?", m_blksize);
-		}
-		send_data(1);
-		break;
-
-	case 17: // CMD17 - READ_SINGLE_BLOCK
-		if (m_harddisk)
-		{
-			m_data[0] = 0x00; // initial R1 response
-			// data token occurs some time after the R1 response.  A2SD expects at least 1
-			// byte of space between R1 and the data packet.
+		case 10:			  // CMD10 - SEND_CID
+			m_data[0] = 0x01; // initial R1 response
+			m_data[1] = 0x00; // throwaway byte before data transfer
 			m_data[2] = 0xfe; // data token
-			u32 blk = (m_cmd[1] << 24) | (m_cmd[2] << 16) | (m_cmd[3] << 8) | m_cmd[4];
-			if (m_type == SD_TYPE_V2)
+			m_data[3] = 'M';  // Manufacturer ID - we'll use M for MAME
+			m_data[4] = 'M';  // OEM ID - MD for MAMEdev
+			m_data[5] = 'D';
+			m_data[6] = 'M'; // Product Name - "MCARD"
+			m_data[7] = 'C';
+			m_data[8] = 'A';
+			m_data[9] = 'R';
+			m_data[10] = 'D';
+			m_data[11] = 0x10; // Product Revision in BCD (1.0)
 			{
-				blk /= m_blksize;
+				u32 uSerial = 0x12345678;
+				m_data[12] = (uSerial >> 24) & 0xff; // PSN - Product Serial Number
+				m_data[13] = (uSerial >> 16) & 0xff;
+				m_data[14] = (uSerial >> 8) & 0xff;
+				m_data[15] = (uSerial & 0xff);
 			}
-			LOGMASKED(LOG_GENERAL, "reading LBA %x\n", blk);
-			hard_disk_read(m_harddisk, blk, &m_data[3]);
+			m_data[16] = 0x01; // MDT - Manufacturing Date
+			m_data[17] = 0x59; // 0x15 9 = 2021, September
+			m_data[18] = 0x00; // CRC7, bit 0 is always 0
 			{
-				util::crc16_t crc16 = util::crc16_creator::simple(&m_data[3], m_blksize);
-				m_data[m_blksize + 3] = (crc16 >> 8) & 0xff;
-				m_data[m_blksize + 4] = (crc16 & 0xff);
+				util::crc16_t crc16 = util::crc16_creator::simple(&m_data[3], 16);
+				m_data[19] = (crc16 >> 8) & 0xff;
+				m_data[20] = (crc16 & 0xff);
 			}
-			send_data(3 + m_blksize + 2);
-		}
-		else
-		{
-			m_data[0] = 0xff; // show an error
-			send_data(1);
-		}
-		break;
+			send_data(3 + 16 + 2, SD_STATE_STBY);
+			break;
 
-	case 24: // CMD24 - WRITE_BLOCK
-		m_data[0] = 0;
-		send_data(1);
-		m_state = SD_STATE_WRITE_WAITFE;
-		break;
-
-	case 41:
-		if (m_bACMD) // ACMD41 - SD_SEND_OP_COND
-		{
+		case 12: // CMD12 - STOP_TRANSMISSION
 			m_data[0] = 0;
-		}
-		else        // CMD41 - illegal
-		{
-			m_data[0] = 0xff;
-		}
-		send_data(1);
-		break;
+			send_data(1,
+					  m_state == SD_STATE_RCV ? SD_STATE_PRG : SD_STATE_TRAN);
+			break;
 
-	case 55: // CMD55 - APP_CMD
-		m_data[0] = 0x01;
-		send_data(1);
-		break;
+		case 16: // CMD16 - SET_BLOCKLEN
+			m_blksize = (u16(m_cmd[3]) << 8) | u16(m_cmd[4]);
+			if (hard_disk_set_block_size(m_harddisk, m_blksize))
+			{
+				m_data[0] = 0;
+			}
+			else
+			{
+				m_data[0] = 0xff; // indicate an error
+				// if false was returned, it means the hard disk is a CHD file, and we can't resize the
+				// blocks on CHD files.
+				logerror("spi_sdcard: Couldn't change block size to %d, wrong "
+						 "CHD file?",
+						 m_blksize);
+			}
+			send_data(1, SD_STATE_TRAN);
+			break;
 
-	case 58: // CMD58 - READ_OCR
-		m_data[0] = 0;
-		if (m_type == SD_TYPE_HC)
+		case 17: // CMD17 - READ_SINGLE_BLOCK
+			if (m_harddisk)
+			{
+				m_data[0] = 0x00; // initial R1 response
+				// data token occurs some time after the R1 response.  A2SD expects at least 1
+				// byte of space between R1 and the data packet.
+				m_data[1] = 0xff;
+				m_data[2] = 0xfe; // data token
+				u32 blk = (u32(m_cmd[1]) << 24) | (u32(m_cmd[2]) << 16) | (u32(m_cmd[3]) << 8) | u32(m_cmd[4]);
+				if (m_type == SD_TYPE_V2)
+				{
+					blk /= m_blksize;
+				}
+				LOGMASKED(LOG_GENERAL, "reading LBA %x\n", blk);
+				hard_disk_read(m_harddisk, blk, &m_data[3]);
+				{
+					util::crc16_t crc16 = util::crc16_creator::simple(&m_data[3], m_blksize);
+					m_data[m_blksize + 3] = (crc16 >> 8) & 0xff;
+					m_data[m_blksize + 4] = (crc16 & 0xff);
+				}
+				send_data(3 + m_blksize + 2, SD_STATE_DATA);
+			}
+			else
+			{
+				m_data[0] = 0xff; // show an error
+				send_data(1, SD_STATE_DATA);
+			}
+			break;
+
+		case 18: // CMD18 - CMD_READ_MULTIPLE_BLOCK
+			if (m_harddisk)
+			{
+				m_data[0] = 0x00; // initial R1 response
+				// data token occurs some time after the R1 response.  A2SD
+				// expects at least 1 byte of space between R1 and the data
+				// packet.
+				m_blknext = (u32(m_cmd[1]) << 24) | (u32(m_cmd[2]) << 16) | (u32(m_cmd[3]) << 8) | u32(m_cmd[4]);
+				if (m_type == SD_TYPE_V2)
+				{
+					m_blknext /= m_blksize;
+				}
+			}
+			else
+			{
+				m_data[0] = 0xff; // show an error
+			}
+			send_data(1, SD_STATE_DATA_MULTI);
+			break;
+
+		case 24: // CMD24 - WRITE_BLOCK
+			m_data[0] = 0;
+			send_data(1, SD_STATE_RCV);
+			break;
+
+		case 41:
+			if (m_bACMD) // ACMD41 - SD_SEND_OP_COND
+			{
+				m_data[0] = 0;
+				send_data(1, SD_STATE_READY); // + SD_STATE_IDLE
+			}
+			else // CMD41 - illegal
+			{
+				m_data[0] = 0xff;
+				send_data(1, SD_STATE_INA);
+			}
+			break;
+
+		case 55: // CMD55 - APP_CMD
+			m_data[0] = 0x01;
+			send_data(1, SD_STATE_IDLE);
+			break;
+
+		case 58: // CMD58 - READ_OCR
+			m_data[0] = 0;
+			if (m_type == SD_TYPE_HC)
+			{
+				m_data[1] = 0x40; // indicate SDHC support
+			}
+			else
+			{
+				m_data[1] = 0;
+			}
+			m_data[2] = 0;
+			m_data[3] = 0;
+			m_data[4] = 0;
+			send_data(5, SD_STATE_DATA);
+			break;
+
+		default:
+			LOGMASKED(LOG_COMMAND, "SDCARD: Unsupported %02x\n", m_cmd[0] & 0x3f);
+			clean_cmd = false;
+			break;
+		}
+
+		// if this is command 55, that's a prefix indicating the next command is an "app command" or "ACMD"
+		if ((m_cmd[0] & 0x3f) == 55)
 		{
-			m_data[1] = 0x40; // indicate SDHC support
+			m_bACMD = true;
 		}
 		else
 		{
-			m_data[1] = 0;
+			m_bACMD = false;
 		}
-		m_data[2] = 0;
-		m_data[3] = 0;
-		m_data[4] = 0;
-		send_data(5);
-		break;
 
-	default:
-		break;
-	}
-
-	// if this is command 55, that's a prefix indicating the next command is an "app command" or "ACMD"
-	if ((m_cmd[0] & 0x3f) == 55)
-	{
-		m_bACMD = true;
-	}
-	else
-	{
-		m_bACMD = false;
+		if (clean_cmd)
+		{
+			for (u8 i = 0; i < 6; i++)
+			{
+				m_cmd[i] = 0xff;
+			}
+		}
 	}
 }
+
+void spi_sdcard_device::change_state(sd_state new_state)
+{
+	// TODO validate if transition is valid using refs below.
+	// REF Figure 4-13:SD Memory Card State Diagram (Transition Mode)
+	// REF Table 4-35:Card State Transition Table
+	m_state = new_state;
+}
diff --git a/src/devices/machine/spi_sdcard.h b/src/devices/machine/spi_sdcard.h
index 23a292664d2..544c884510f 100644
--- a/src/devices/machine/spi_sdcard.h
+++ b/src/devices/machine/spi_sdcard.h
@@ -21,7 +21,7 @@ public:
 	devcb_write_line write_miso;
 
 protected:
-	enum
+	enum sd_type : u8
 	{
 		SD_TYPE_V2 = 0,
 		SD_TYPE_HC
@@ -35,24 +35,41 @@ protected:
 
 	required_device<harddisk_image_device> m_image;
 
-	int m_type;
+	sd_type m_type;
 
 private:
-	enum
+	enum sd_state : u8
 	{
+		//REF Table 4-1:Overview of Card States vs. Operation Mode
 		SD_STATE_IDLE = 0,
+		SD_STATE_READY,
+		SD_STATE_IDENT,
+		SD_STATE_STBY,
+		SD_STATE_TRAN,
+		SD_STATE_DATA,
+		SD_STATE_DATA_MULTI, // synthetical state for this implementation
+		SD_STATE_RCV,
+		SD_STATE_PRG,
+		SD_STATE_DIS,
+		SD_STATE_INA,
+
+		//FIXME Existing states wich must be revisited
 		SD_STATE_WRITE_WAITFE,
 		SD_STATE_WRITE_DATA
 	};
+	sd_state m_state;
 
-	void send_data(int count);
+	void send_data(u16 count, sd_state new_state);
 	void do_command();
+	void change_state(sd_state new_state);
 
 	u8 m_data[520], m_cmd[6];
 	hard_disk_file *m_harddisk;
 
-	u8 m_in_latch, m_out_latch;
-	int m_cmd_ptr, m_state, m_out_ptr, m_out_count, m_ss, m_in_bit, m_cur_bit, m_write_ptr, m_blksize;
+	int m_ss, m_in_bit;
+	u8 m_in_latch, m_out_latch, m_cur_bit;
+	u16 m_out_count, m_out_ptr, m_write_ptr, m_blksize;
+	u32 m_blknext;
 	bool m_bACMD;
 };