diff --git a/emul/hw/rc2014/Makefile b/emul/hw/rc2014/Makefile index 3ba5a7b..1ed63a4 100644 --- a/emul/hw/rc2014/Makefile +++ b/emul/hw/rc2014/Makefile @@ -1,5 +1,5 @@ EXTOBJS = ../../emul.o ../../libz80/libz80.o -OBJS = acia.o classic.o +OBJS = acia.o sdc.o classic.o TARGET = classic .PHONY: all diff --git a/emul/hw/rc2014/classic.c b/emul/hw/rc2014/classic.c index 0c41e4a..9d7ba16 100644 --- a/emul/hw/rc2014/classic.c +++ b/emul/hw/rc2014/classic.c @@ -13,13 +13,18 @@ #include #include "../../emul.h" #include "acia.h" +#include "sdc.h" #define RAMSTART 0x8000 #define ACIA_CTL_PORT 0x80 #define ACIA_DATA_PORT 0x81 +#define SDC_CSHIGH 0x06 +#define SDC_CSLOW 0x05 +#define SDC_SPI 0x04 #define MAX_ROMSIZE 0x2000 static ACIA acia; +static SDC sdc; static uint8_t iord_acia_ctl() { @@ -41,10 +46,30 @@ static void iowr_acia_data(uint8_t val) acia_data_wr(&acia, val); } +static uint8_t iord_sdc_spi() +{ + return sdc_spi_rd(&sdc); +} + +static void iowr_sdc_spi(uint8_t val) +{ + sdc_spi_wr(&sdc, val); +} + +static void iowr_sdc_cshigh(uint8_t val) +{ + sdc_cshigh(&sdc); +} + +static void iowr_sdc_cslow(uint8_t val) +{ + sdc_cslow(&sdc); +} + int main(int argc, char *argv[]) { - if (argc != 2) { - fprintf(stderr, "Usage: ./classic /path/to/rom\n"); + if (argc < 2) { + fprintf(stderr, "Usage: ./classic /path/to/rom [sdcard.img]\n"); return 1; } FILE *fp = fopen(argv[1], "r"); @@ -81,10 +106,19 @@ int main(int argc, char *argv[]) } acia_init(&acia); + sdc_init(&sdc); + if (argc == 3) { + fprintf(stderr, "Setting up SD card image\n"); + sdc.fp = fopen(argv[2], "r+"); + } m->iord[ACIA_CTL_PORT] = iord_acia_ctl; m->iord[ACIA_DATA_PORT] = iord_acia_data; m->iowr[ACIA_CTL_PORT] = iowr_acia_ctl; m->iowr[ACIA_DATA_PORT] = iowr_acia_data; + m->iord[SDC_SPI] = iord_sdc_spi; + m->iowr[SDC_SPI] = iowr_sdc_spi; + m->iowr[SDC_CSHIGH] = iowr_sdc_cshigh; + m->iowr[SDC_CSLOW] = iowr_sdc_cslow; char tosend = 0; while (emul_step()) { @@ -126,5 +160,8 @@ int main(int argc, char *argv[]) tcsetattr(0, TCSADRAIN, &saved_term); emul_printdebug(); } + if (sdc.fp) { + fclose(sdc.fp); + } return 0; } diff --git a/emul/hw/rc2014/sdc.c b/emul/hw/rc2014/sdc.c new file mode 100644 index 0000000..b684048 --- /dev/null +++ b/emul/hw/rc2014/sdc.c @@ -0,0 +1,206 @@ +#include +#include "sdc.h" + +// Add data to crc with polynomial 0x1021 +// https://stackoverflow.com/a/23726131 +static uint16_t crc16(uint16_t crc, uint8_t data) +{ + uint8_t x = crc >> 8 ^ data; + x ^= x>>4; + crc = (crc << 8) ^ ((uint16_t)(x << 12)) ^ ((uint16_t)(x <<5)) ^ ((uint16_t)x); + return crc; +} + +void sdc_init(SDC *sdc) +{ + sdc->selected = false; + sdc->initstat = 0; + sdc->recvidx = 0; + sdc->sendidx = -1; + sdc->resp = 0xff; + sdc->fp = NULL; + sdc->cmd17bytes = -1; + sdc->cmd24bytes = -2; +} + +void sdc_cslow(SDC *sdc) +{ + sdc->selected = true; +} + +void sdc_cshigh(SDC *sdc) +{ + sdc->selected = false; +} + +void sdc_spi_wr(SDC *sdc, uint8_t val) +{ + if (!sdc->selected) { + return; + } + sdc->resp = 0xff; + if (sdc->initstat < 8) { + // not woken yet. + sdc->initstat++; + return; + } + if (sdc->sendidx >= 0) { + sdc->resp = sdc->sendbuf[sdc->sendidx++]; + if (sdc->sendidx == 5) { + sdc->sendidx = -1; + } + return; + } + if (sdc->cmd17bytes >= 0) { + if (sdc->fp) { + sdc->resp = getc(sdc->fp); + } + sdc->crc16 = crc16(sdc->crc16, sdc->resp); + sdc->cmd17bytes++; + if (sdc->cmd17bytes == 512) { + sdc->sendbuf[3] = sdc->crc16 >> 8; + sdc->sendbuf[4] = sdc->crc16 & 0xff; + sdc->sendidx = 3; + sdc->cmd17bytes = -1; + } + return; + } + if (sdc->cmd24bytes == -1) { + if (val == 0xff) { + // it's ok to receive idle bytes before the data token. + return; + } + if (val == 0xfe) { + // data token, good + sdc->cmd24bytes = 0; + } else { + // something is wrong, cancel cmd24 + sdc->cmd24bytes = -2; + } + return; + } + if (sdc->cmd24bytes >= 0) { + if (sdc->cmd24bytes < 512) { + if (sdc->fp) { + putc(val, sdc->fp); + } + sdc->crc16 = crc16(sdc->crc16, val); + } else if (sdc->cmd24bytes == 512) { + // CRC MSB + if (val == (sdc->crc16>>8)) { + fprintf(stderr, "Good CRC16 MSB\n"); + } else { + fprintf(stderr, "Bad CRC16 MSB\n"); + } + } else { + if (val == (sdc->crc16&0xff)) { + fprintf(stderr, "Good CRC16 LSB\n"); + } else { + fprintf(stderr, "Bad CRC16 LSB\n"); + } + // valid response for CMD24 + sdc->sendbuf[4] = 0x05; + sdc->sendidx = 4; + sdc->cmd24bytes = -3; + } + sdc->cmd24bytes++; + return; + } + if ((sdc->recvidx == 0) && ((val > 0x7f) || (val < 0x40))) { + // not a command + return; + } + sdc->recvbuf[sdc->recvidx++] = val; + if (sdc->recvidx < 6) { + // incomplete command + return; + } + // Command complete + val &= 0x3f; + sdc->recvidx = 0; + uint8_t *b = sdc->recvbuf; + uint8_t cmd = b[0] & 0x3f; + uint16_t arg1 = (b[1] << 8) | b[2]; + uint16_t arg2 = (b[3] << 8) | b[4]; + if (sdc->initstat == 8) { + // At this stage, we're expecting CMD0 + if (cmd == 0) { + sdc->initstat++; + sdc->sendbuf[4] = 0x01; + sdc->sendidx = 4; + } + return; + } + if (sdc->initstat == 9) { + // At this stage, we're expecting CMD8 with 0x1aa arg2 + if ((cmd == 8) && (arg2 == 0x01aa)) { + sdc->initstat++; + sdc->sendbuf[0] = 0x01; + sdc->sendbuf[1] = 0; + sdc->sendbuf[2] = 0; + sdc->sendbuf[3] = 0x01; + sdc->sendbuf[4] = 0xaa; + sdc->sendidx = 0; + } else { + sdc-> initstat = 8; + } + return; + } + if (sdc->initstat == 10) { + // At this stage, we're expecting CMD55 + if (cmd == 55) { + sdc->initstat++; + sdc->sendbuf[4] = 0x01; + sdc->sendidx = 4; + } else { + sdc->initstat = 8; + } + return; + } + if (sdc->initstat == 11) { + // At this stage, we're expecting CMD41 + if ((cmd == 41) && (arg1 == 0x4000)) { + sdc->initstat++; + sdc->sendbuf[4] = 0x00; + sdc->sendidx = 4; + } else { + sdc->initstat = 8; + } + return; + } + // We have a fully initialized card. + if (cmd == 17) { + if (sdc->fp) { + fseek(sdc->fp, arg2*512, SEEK_SET); + } + sdc->sendbuf[3] = 0x00; + // data token + sdc->sendbuf[4] = 0xfe; + sdc->sendidx = 3; + sdc->cmd17bytes = 0; + sdc->crc16 = 0; + return; + } + if (cmd == 24) { + fprintf(stderr, "cmd24\n"); + if (sdc->fp) { + fseek(sdc->fp, arg2*512, SEEK_SET); + } + sdc->sendbuf[4] = 0x00; + sdc->sendidx = 4; + sdc->cmd24bytes = -1; + sdc->crc16 = 0; + return; + } + // Simulate success for any unknown command. + sdc->sendbuf[4] = 0x00; + sdc->sendidx = 4; +} + +uint8_t sdc_spi_rd(SDC *sdc) +{ + if (!sdc->selected) { + return 0xff; + } + return sdc->resp; +} diff --git a/emul/hw/rc2014/sdc.h b/emul/hw/rc2014/sdc.h new file mode 100644 index 0000000..6a84ecb --- /dev/null +++ b/emul/hw/rc2014/sdc.h @@ -0,0 +1,37 @@ +#include +#include + +typedef struct { + bool selected; + // Initialization status. 0 == not woken 8 == woken 9 == CMD0 received + // 10 == CMD8 received, 11 == CMD55 received, 12 == CMD41 received (fully + // initialized). + unsigned int initstat; + // We receive commands into this buffer. + uint8_t recvbuf[6]; + // Where the next SPI byte should be stored in recvbuf. + unsigned int recvidx; + // Buffer to the arguments for a response + uint8_t sendbuf[5]; + // Index of the next byte from sendbuf we should return. If -1, buffer is + // empty. + int sendidx; + // One byte response. When all other response buffers are empty, return + // this. + uint8_t resp; + // File used for contents read/write + FILE *fp; + // number of bytes read into the current CMD17. -1 means no CMD17 active. + int cmd17bytes; + // number of bytes received for the current CMD24. -2 means no CMD24 active. + // -1 means we're still waiting for the data token. + int cmd24bytes; + // running crc16 during read and write operations. + uint16_t crc16; +} SDC; + +void sdc_init(SDC *sdc); +void sdc_cslow(SDC *sdc); +void sdc_cshigh(SDC *sdc); +void sdc_spi_wr(SDC *sdc, uint8_t val); +uint8_t sdc_spi_rd(SDC *sdc);